has_state_machine 1.1.0 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 902498121bd57b97de373beaa68557c3591de8a221d524af5b9033be1cca8a73
4
- data.tar.gz: fb4806d204772d488c4fb2bc69360cd989edf4c8a5312508b37868d961adf059
3
+ metadata.gz: 4d16c6b958f2e470af9f3120259e802badad7fcd6b8a4ea1d004c84f64e8d09d
4
+ data.tar.gz: 2f9933db38477a1b1020303ab9848aa122fe7e699d153315b830144ffdbfd1a6
5
5
  SHA512:
6
- metadata.gz: 1baeee9d74f64325427b52e2e867cab05db7848f384316b8175cbb5d49a2c4030dcf905020ac8091c495f6cfa20f8dcae657bcffd78b86987e9c867665f5cf42
7
- data.tar.gz: 7e998865b342452f802266b3408bd673b26fb622672ed48ff18cb384259c34a7ec1f591a9ed99efb12f33364e5a1c9bff33d9b95bd4fbac3794ff9d3f2ccdd25
6
+ metadata.gz: 35f89aad901be7f976ee42573449c0d7412a633285e41f869f50d7be3215da2752470cf5c4b1299d483605f90b8925f5baa5065729b06f2121678518beeafdb4
7
+ data.tar.gz: cb4e1a772fa2aa9828adfd3ab6bb548e5f489f403f4574cc464e9fa77f4633b886de5c486e4bb9b014eeb71c8ede380fab19320f8b85023e9643b38b3b9866c8
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HasStateMachine
4
- VERSION = "1.1.0"
4
+ VERSION = "1.2.0"
5
5
  end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby_lsp/addon"
4
+
5
+ require "has_state_machine/version"
6
+ require_relative "definition"
7
+
8
+ ::RubyLsp::Addon.depend_on_ruby_lsp!(">= 0.18.0", "< 1.0") if ::RubyLsp::Addon.respond_to?(:depend_on_ruby_lsp!)
9
+
10
+ module RubyLsp
11
+ module HasStateMachine
12
+ class Addon < ::RubyLsp::Addon
13
+ def activate(global_state, outgoing_queue)
14
+ @global_state = global_state
15
+ @rails_client = register_rails_server_addon(outgoing_queue)
16
+ end
17
+
18
+ def deactivate
19
+ @global_state = nil
20
+ @rails_client = nil
21
+ end
22
+
23
+ def name
24
+ "Has State Machine"
25
+ end
26
+
27
+ def version
28
+ ::HasStateMachine::VERSION
29
+ end
30
+
31
+ def create_definition_listener(response_builder, uri, node_context, dispatcher)
32
+ Definition.new(
33
+ response_builder,
34
+ uri,
35
+ node_context,
36
+ dispatcher,
37
+ index: @global_state&.index,
38
+ rails_client: @rails_client
39
+ )
40
+ end
41
+
42
+ private
43
+
44
+ def register_rails_server_addon(outgoing_queue)
45
+ register_rails_runner_client
46
+ rescue => error
47
+ handle_rails_registration_error(outgoing_queue, error)
48
+ end
49
+
50
+ def register_rails_runner_client
51
+ rails_addon = ::RubyLsp::Addon.get("Ruby LSP Rails", ">= 0") if ::RubyLsp::Addon.respond_to?(:get)
52
+ return unless rails_addon&.respond_to?(:rails_runner_client)
53
+
54
+ client = rails_addon.rails_runner_client
55
+ return unless client.respond_to?(:register_server_addon)
56
+
57
+ client.register_server_addon(File.expand_path("rails_server_addon.rb", __dir__))
58
+ client
59
+ end
60
+
61
+ def handle_rails_registration_error(outgoing_queue, error)
62
+ unless addon_not_found?(error)
63
+ log(outgoing_queue, "Has State Machine Ruby LSP Rails integration unavailable: #{error.message}")
64
+ end
65
+
66
+ nil
67
+ end
68
+
69
+ def addon_not_found?(error)
70
+ if defined?(::RubyLsp::Addon::AddonNotFoundError)
71
+ return true if error.is_a?(::RubyLsp::Addon::AddonNotFoundError)
72
+ end
73
+
74
+ error.class.name&.end_with?("AddonNotFoundError")
75
+ end
76
+
77
+ def log(outgoing_queue, message)
78
+ return if outgoing_queue.nil? || outgoing_queue.closed?
79
+ return unless defined?(::RubyLsp::Notification)
80
+
81
+ outgoing_queue << ::RubyLsp::Notification.window_log_message(message)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "workflow_resolver"
4
+
5
+ module RubyLsp
6
+ module HasStateMachine
7
+ class Definition
8
+ SERVER_ADDON_NAME = "has_state_machine"
9
+
10
+ def initialize(response_builder, _uri, node_context, dispatcher, index: nil, rails_client: nil)
11
+ @response_builder = response_builder
12
+ @node_context = node_context
13
+ @index = index
14
+ @rails_client = rails_client
15
+
16
+ dispatcher.register(self, :on_call_node_enter)
17
+ end
18
+
19
+ def on_call_node_enter(node)
20
+ return unless current_target?(node)
21
+ return unless resolved_model_name
22
+
23
+ if object_call?(node)
24
+ push_entries(constant_entries(resolved_model_name))
25
+ elsif object_method_call?(node)
26
+ method_name = message(node)
27
+ entries = method_entries(resolved_model_name, method_name)
28
+ entries = association_entries(resolved_model_name, method_name) if entries.empty?
29
+
30
+ push_entries(entries)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :index, :node_context, :rails_client, :response_builder
37
+
38
+ def current_target?(node)
39
+ target = node_context.node if node_context.respond_to?(:node)
40
+ call_node = node_context.call_node if node_context.respond_to?(:call_node)
41
+
42
+ node.equal?(target) || node.equal?(call_node)
43
+ end
44
+
45
+ def resolved_model_name
46
+ return @resolved_model_name if defined?(@resolved_model_name)
47
+ return @resolved_model_name = nil unless current_class_name
48
+
49
+ @resolved_model_name = model_name_from_rails(workflow_namespace) || convention_model_name
50
+ end
51
+
52
+ def convention_model_name
53
+ return @convention_model_name if defined?(@convention_model_name)
54
+
55
+ @convention_model_name = current_class_name && WorkflowResolver.model_name_for(current_class_name)
56
+ end
57
+
58
+ def workflow_namespace
59
+ return @workflow_namespace if defined?(@workflow_namespace)
60
+
61
+ @workflow_namespace = current_class_name && WorkflowResolver.workflow_namespace_for(current_class_name)
62
+ end
63
+
64
+ def current_class_name
65
+ return @current_class_name if defined?(@current_class_name)
66
+ return @current_class_name = nil unless node_context.respond_to?(:nesting)
67
+
68
+ @current_class_name = class_name_from_nesting(node_context.nesting)
69
+ end
70
+
71
+ def class_name_from_nesting(nesting)
72
+ parts = nesting.map(&:to_s).reject(&:empty?)
73
+ return if parts.empty?
74
+
75
+ # Nesting entries may already contain "::" (e.g. "Post::Draft").
76
+ parts.join("::").gsub(/:{3,}/, "::")
77
+ end
78
+
79
+ def model_name_from_rails(workflow_namespace)
80
+ return unless workflow_namespace && rails_client
81
+
82
+ result = rails_client.delegate_request(
83
+ server_addon_name: SERVER_ADDON_NAME,
84
+ request_name: "model_for_workflow_namespace",
85
+ workflow_namespace: workflow_namespace
86
+ )
87
+
88
+ response_name(result)
89
+ rescue
90
+ nil
91
+ end
92
+
93
+ def association_entries(model_name, association_name)
94
+ association_model_name = association_model_name(model_name, association_name)
95
+ return [] unless association_model_name
96
+
97
+ constant_entries(association_model_name)
98
+ end
99
+
100
+ def association_model_name(model_name, association_name)
101
+ return unless rails_client&.respond_to?(:association_target)
102
+
103
+ result = rails_client.association_target(model_name: model_name, association_name: association_name)
104
+ response_name(result)
105
+ rescue
106
+ nil
107
+ end
108
+
109
+ def response_name(result)
110
+ return unless result
111
+
112
+ result[:name] || result["name"]
113
+ end
114
+
115
+ def constant_entries(name)
116
+ return [] unless name && index
117
+
118
+ Array(index[name])
119
+ end
120
+
121
+ def method_entries(model_name, method_name)
122
+ return [] unless index && method_name
123
+
124
+ if index.respond_to?(:resolve_method)
125
+ entries = index.resolve_method(method_name, model_name)
126
+ return Array(entries)
127
+ end
128
+
129
+ Array(index[method_name]).select { |entry| entry_owner_name(entry) == model_name }
130
+ end
131
+
132
+ def entry_owner_name(entry)
133
+ owner = entry.owner if entry.respond_to?(:owner)
134
+ owner.name if owner.respond_to?(:name)
135
+ end
136
+
137
+ def push_entries(entries)
138
+ entries.each do |entry|
139
+ response_builder << location_for(entry)
140
+ end
141
+ end
142
+
143
+ def location_for(entry)
144
+ return entry unless defined?(::RubyLsp::Interface::Location)
145
+
146
+ location = entry_location(entry)
147
+ return entry unless location
148
+
149
+ ::RubyLsp::Interface::Location.new(
150
+ uri: entry.uri.to_s,
151
+ range: range_for(location)
152
+ )
153
+ end
154
+
155
+ def entry_location(entry)
156
+ return entry.location if entry.respond_to?(:location)
157
+ entry.name_location if entry.respond_to?(:name_location)
158
+ end
159
+
160
+ def range_for(location)
161
+ ::RubyLsp::Interface::Range.new(
162
+ start: ::RubyLsp::Interface::Position.new(
163
+ line: location.start_line - 1,
164
+ character: location.start_column
165
+ ),
166
+ end: ::RubyLsp::Interface::Position.new(
167
+ line: location.end_line - 1,
168
+ character: location.end_column
169
+ )
170
+ )
171
+ end
172
+
173
+ def object_method_call?(node)
174
+ message(node) && object_call?(receiver(node))
175
+ end
176
+
177
+ def object_call?(node)
178
+ node && receiver(node).nil? && message(node) == "object"
179
+ end
180
+
181
+ def receiver(node)
182
+ node.receiver if node.respond_to?(:receiver)
183
+ end
184
+
185
+ def message(node)
186
+ node.message.to_s if node.respond_to?(:message) && node.message
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby_lsp/ruby_lsp_rails/server"
4
+
5
+ module RubyLsp
6
+ module HasStateMachine
7
+ class RailsServerAddon < ::RubyLsp::Rails::ServerAddon
8
+ def name
9
+ "has_state_machine"
10
+ end
11
+
12
+ def execute(request, params)
13
+ with_request_error_handling(request) do
14
+ case request
15
+ when "model_for_workflow_namespace"
16
+ send_result(model_for_workflow_namespace(params.fetch("workflow_namespace")))
17
+ else
18
+ raise NotImplementedError, "Unknown request: #{request}"
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def model_for_workflow_namespace(workflow_namespace)
26
+ model = models_by_workflow_namespace[workflow_namespace]
27
+ return unless model
28
+
29
+ {name: model.name}
30
+ end
31
+
32
+ def models_by_workflow_namespace
33
+ @models_by_workflow_namespace ||= active_record_models.each_with_object({}) do |model, index|
34
+ next unless model.respond_to?(:workflow_namespace)
35
+
36
+ index[model.workflow_namespace] = model
37
+ end
38
+ end
39
+
40
+ def active_record_models
41
+ @active_record_models ||= begin
42
+ ::Rails.application&.eager_load!
43
+ ::ActiveRecord::Base.descendants.reject(&:abstract_class?)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLsp
4
+ module HasStateMachine
5
+ module WorkflowResolver
6
+ WORKFLOW_PREFIX = "Workflow::"
7
+
8
+ module_function
9
+
10
+ def model_name_for(workflow_class_name)
11
+ namespace = workflow_namespace_for(workflow_class_name)
12
+ return unless namespace&.start_with?(WORKFLOW_PREFIX)
13
+
14
+ name = namespace.delete_prefix(WORKFLOW_PREFIX)
15
+ return if name.empty?
16
+
17
+ name
18
+ end
19
+
20
+ def workflow_namespace_for(workflow_class_name)
21
+ namespace = workflow_class_name.to_s.delete_prefix("::").sub(/::[^:]+\z/, "")
22
+ return if namespace.empty?
23
+
24
+ namespace
25
+ end
26
+ end
27
+ end
28
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: has_state_machine
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Hargett
@@ -31,6 +31,9 @@ dependencies:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.5'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '2.0'
34
37
  type: :development
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
@@ -38,6 +41,23 @@ dependencies:
38
41
  - - ">="
39
42
  - !ruby/object:Gem::Version
40
43
  version: '1.5'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: nokogiri
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "<"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.19'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "<"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.19'
41
61
  - !ruby/object:Gem::Dependency
42
62
  name: pry
43
63
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +100,20 @@ dependencies:
80
100
  - - ">="
81
101
  - !ruby/object:Gem::Version
82
102
  version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: parallel
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "<"
108
+ - !ruby/object:Gem::Version
109
+ version: '2.0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "<"
115
+ - !ruby/object:Gem::Version
116
+ version: '2.0'
83
117
  - !ruby/object:Gem::Dependency
84
118
  name: appraisal
85
119
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +142,90 @@ dependencies:
108
142
  - - "~>"
109
143
  - !ruby/object:Gem::Version
110
144
  version: '5.1'
145
+ - !ruby/object:Gem::Dependency
146
+ name: ruby-lsp
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0.18'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0.18'
159
+ - !ruby/object:Gem::Dependency
160
+ name: ruby-lsp-rails
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: 0.3.17
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: 0.3.17
173
+ - !ruby/object:Gem::Dependency
174
+ name: base64
175
+ requirement: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ type: :development
181
+ prerelease: false
182
+ version_requirements: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ - !ruby/object:Gem::Dependency
188
+ name: bigdecimal
189
+ requirement: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ type: :development
195
+ prerelease: false
196
+ version_requirements: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
201
+ - !ruby/object:Gem::Dependency
202
+ name: drb
203
+ requirement: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: '0'
208
+ type: :development
209
+ prerelease: false
210
+ version_requirements: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ - !ruby/object:Gem::Dependency
216
+ name: mutex_m
217
+ requirement: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - ">="
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
222
+ type: :development
223
+ prerelease: false
224
+ version_requirements: !ruby/object:Gem::Requirement
225
+ requirements:
226
+ - - ">="
227
+ - !ruby/object:Gem::Version
228
+ version: '0'
111
229
  description: HasStateMachine uses ruby classes to make creating a finite state machine
112
230
  in your ActiveRecord models a breeze.
113
231
  email:
@@ -128,6 +246,10 @@ files:
128
246
  - lib/has_state_machine/state.rb
129
247
  - lib/has_state_machine/state_helpers.rb
130
248
  - lib/has_state_machine/version.rb
249
+ - lib/ruby_lsp/has_state_machine/addon.rb
250
+ - lib/ruby_lsp/has_state_machine/definition.rb
251
+ - lib/ruby_lsp/has_state_machine/rails_server_addon.rb
252
+ - lib/ruby_lsp/has_state_machine/workflow_resolver.rb
131
253
  homepage: https://www.github.com/encampment/has_state_machine
132
254
  licenses:
133
255
  - MIT