ruby-lsp-ree 0.1.15 → 0.1.17

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: 7f3673076ca0aee98f0f92d6db25a2939ae21a58464a70e1a1e534bfef3458d0
4
- data.tar.gz: 2191d354bbe75911cc230ca20665e5882e15eabee5de30801db1960990d42c3b
3
+ metadata.gz: 385dfd53ada066f3fbbe41421c4b425773d26e70fd129a68a43d623c77840597
4
+ data.tar.gz: 1f9d2120db749bb2bf71a56653143d847dad3463fcc1a7528b790be2ea5e667e
5
5
  SHA512:
6
- metadata.gz: ec2b1c6e15509e7e8f18758ef3a6912c14dccbdc6348d353331894fc53efbd072b043e49b1ee933c2b552e11404a4f322f8336aa29f267f3a842724d67fc8d7a
7
- data.tar.gz: c584181adbf4ed1a02dc2545c1b65cf4ada307ade2d46aee661a97319adf43d67e6d21bb47f7b05d73bc4a2b913053ca990cae0cf1722587c350be942151e385
6
+ metadata.gz: 110bcd5a8efdc990f09b8c62c0448799e4573187d630da481390594f5544137c7445971bb3936aed2c1bc76c638843d8084fcbe0c3dac2fe4fb26fe04f08ce6e
7
+ data.tar.gz: c2f53768271f71890342e40dfd7762102b7b63323f4c68386a3503847ecd15635ad2adfe338f030ec410f1d46d944206faaa20f5c158d9c769a4031a40fe1f2a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## [0.1.17] - 2025-04-28
2
+
3
+ - add missing imports: improved handling imports with the same name
4
+ - add missing imports: handle more cases with missing imports (nested calls, predicates, etc.)
5
+
6
+ ## [0.1.16] - 2025-04-24
7
+
8
+ - formatter: add missing imports
9
+
1
10
  ## [0.1.15] - 2025-04-18
2
11
 
3
12
  - autocomplete - don't overwrite existing arguments
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-lsp-ree (0.1.15)
4
+ ruby-lsp-ree (0.1.17)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -32,7 +32,7 @@ To switch off/on formatter features, use Ruby LSP addon settings:
32
32
  }
33
33
  }
34
34
  ```
35
- available formatters: `SortLinksFormatter`, `MissingErrorDefinitionsFormatter`, `MissingErrorContractsFormatter`, `MissingErrorLocalesFormatter`, `UnusedLinksFormatter`
35
+ available formatters: `SortLinksFormatter`, `MissingErrorDefinitionsFormatter`, `MissingErrorContractsFormatter`, `MissingErrorLocalesFormatter`, `UnusedLinksFormatter`, `MissingImportsFormatter`
36
36
 
37
37
  ## Functions
38
38
 
@@ -33,7 +33,7 @@ module RubyLsp
33
33
  new_text = "\n" + link_text
34
34
 
35
35
  if @parsed_doc.has_blank_links_container?
36
- new_text = "\sdo#{link_text}\n\s\send\n"
36
+ new_text = "\sdo#{new_text}\n\s\send\n"
37
37
  end
38
38
 
39
39
  range = get_range_for_fn_insert(@parsed_doc, link_text)
@@ -4,7 +4,7 @@ module RubyLsp
4
4
  def self.call(source, uri, message_queue, index)
5
5
  new(message_queue, index).call(source, uri)
6
6
  rescue => e
7
- $stderr.puts("error in #{self.class}: #{e.message} : #{e.backtrace.first}")
7
+ $stderr.puts("error in #{self}: #{e.message} : #{e.backtrace.first}")
8
8
  source
9
9
  end
10
10
 
@@ -0,0 +1,74 @@
1
+ require_relative 'base_formatter'
2
+ require_relative "../ree_object_finder"
3
+
4
+ module RubyLsp
5
+ module Ree
6
+ class MissingImportsFormatter < BaseFormatter
7
+ include RubyLsp::Ree::ReeLspUtils
8
+
9
+ def call(source, uri)
10
+ return source unless @index
11
+
12
+ parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_source(source)
13
+ return source if !parsed_doc || !parsed_doc.has_root_class?
14
+
15
+ finder = ReeObjectFinder.new(@index)
16
+ editor = RubyLsp::Ree::ReeSourceEditor.new(source)
17
+
18
+ current_package = package_name_from_uri(uri)
19
+
20
+
21
+ method_calls = parsed_doc.parse_method_calls
22
+ filtered_method_calls = filter_method_calls(parsed_doc, method_calls)
23
+ objects_to_add = filtered_method_calls.map{ |fn_call|
24
+ ree_objects = finder.find_objects(fn_call.name.to_s)
25
+ choose_object_to_add(ree_objects, current_package)
26
+ }.compact
27
+
28
+ objects_to_add.uniq!{ |obj| obj.name }
29
+ objects_to_add.reject!{ |obj| parsed_doc.includes_linked_object?(obj.name) }
30
+ return editor.source if objects_to_add.size == 0
31
+
32
+ editor.add_links(parsed_doc, objects_to_add, current_package)
33
+ editor.source
34
+ end
35
+
36
+ private
37
+
38
+ def filter_method_calls(parsed_doc, method_calls)
39
+ parsed_doc.parse_instance_methods
40
+
41
+ method_calls.select do |method_call|
42
+ if !method_call.method_name
43
+ true
44
+ elsif parsed_doc.doc_instance_methods.map(&:name).include?(method_call.name)
45
+ false
46
+ else
47
+ method_obj = parsed_doc.doc_instance_methods.detect{ _1.name == method_call.method_name }
48
+ local_variables = method_obj.parse_local_variables.map(&:name)
49
+ method_params = method_obj.param_names
50
+
51
+ !local_variables.include?(method_call.name) && !method_params.include?(method_call.name)
52
+ end
53
+ end
54
+ end
55
+
56
+ def choose_object_to_add(ree_objects, current_package)
57
+ return if !ree_objects || ree_objects.size == 0
58
+ return ree_objects.first if ree_objects.size == 1
59
+
60
+ current_package_object = ree_objects.detect{ _1.object_package == current_package }
61
+ return current_package_object if current_package_object
62
+
63
+ package_names = ree_objects.map(&:object_package)
64
+ if package_names.sort == ['ree_date', 'ree_datetime'].sort
65
+ return ree_objects.detect{ _1.object_package == 'ree_datetime' }
66
+ end
67
+
68
+ ree_object = ree_objects.first
69
+ ree_object.set_package!('FILL_PACKAGE')
70
+ ree_object
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,131 @@
1
+ class RubyLsp::Ree::CallObjectsParser
2
+ attr_reader :parsed_doc
3
+
4
+ class CallObject
5
+ attr_reader :name, :type, :receiver_name, :method_name
6
+
7
+ def initialize(name:, type:, receiver_name: nil)
8
+ @name = name
9
+ @type = type
10
+ @receiver_name = receiver_name
11
+ @method_name = nil
12
+ end
13
+
14
+ def set_method_name(method_name)
15
+ @method_name = method_name
16
+ end
17
+ end
18
+
19
+ def initialize(parsed_doc)
20
+ @parsed_doc = parsed_doc
21
+ end
22
+
23
+ def class_call_objects
24
+ call_objects = []
25
+ return unless parsed_doc.has_body?
26
+
27
+ call_objects += parse_body_call_objects(parsed_doc.class_node.body.body)
28
+
29
+ parsed_doc.parse_instance_methods
30
+
31
+ parsed_doc.doc_instance_methods.each do |doc_instance_method|
32
+ call_objects += method_call_objects(doc_instance_method)
33
+ end
34
+
35
+ call_objects
36
+ end
37
+
38
+ def method_call_objects(method_object)
39
+ method_body = method_object.method_body
40
+ return [] unless method_body
41
+
42
+ call_nodes = parse_body_call_objects(method_body)
43
+ call_expressions = [] # don't parse call expressions for now parse_body_call_expressions(method_body)
44
+
45
+ call_objects = call_nodes + call_expressions
46
+
47
+ call_objects.each{ |call_object| call_object.set_method_name(method_object.name) }
48
+ call_objects
49
+ end
50
+
51
+ private
52
+
53
+ def parse_body_call_objects(node_body)
54
+ call_objects = []
55
+
56
+ node_body.each do |node|
57
+ if node.is_a?(Prism::CallNode)
58
+ if node.receiver
59
+ receiver = get_first_receiver(node)
60
+
61
+ if receiver.is_a?(Prism::CallNode)
62
+ call_objects += parse_body_call_objects([receiver])
63
+ end
64
+ else
65
+ call_objects << CallObject.new(name: node.name, type: :method_call)
66
+ end
67
+
68
+ call_objects += parse_call_objects_from_args(node.arguments)
69
+ else
70
+ if node.respond_to?(:elements)
71
+ call_objects += parse_body_call_objects(node.elements)
72
+ end
73
+
74
+ if node.respond_to?(:predicate)
75
+ call_objects += parse_body_call_objects([node.predicate])
76
+ end
77
+
78
+ if node.respond_to?(:statements)
79
+ call_objects += parse_body_call_objects(node.statements.body)
80
+ end
81
+
82
+ if node.respond_to?(:block) && node.block && node.block.is_a?(Prism::BlockNode)
83
+ call_objects += parse_body_call_objects(get_method_body(node.block))
84
+ end
85
+
86
+ if node.respond_to?(:value) && node.value
87
+ call_objects += parse_body_call_objects([node.value])
88
+ end
89
+ end
90
+ end
91
+
92
+ call_objects
93
+ end
94
+
95
+ def parse_call_objects_from_args(node_arguments)
96
+ return [] if !node_arguments || !node_arguments.arguments
97
+ parse_body_call_objects(node_arguments.arguments)
98
+ end
99
+
100
+ def parse_body_call_expressions(node_body)
101
+ call_expressions = []
102
+
103
+ node_body.each do |node|
104
+ if node.respond_to?(:block) && node.block && node.block.is_a?(Prism::BlockArgumentNode) && node.block.expression.is_a?(Prism::SymbolNode)
105
+ call_expressions << CallObject.new(name: node.block.expression.unescaped.to_sym, type: :proc_to_sym)
106
+ end
107
+ end
108
+
109
+ call_expressions
110
+ end
111
+
112
+ def get_method_body(node)
113
+ return unless node.body
114
+
115
+ if node.body.is_a?(Prism::BeginNode)
116
+ node.body.statements.body
117
+ else
118
+ node.body.body
119
+ end
120
+ end
121
+
122
+ def get_first_receiver(node)
123
+ return nil unless node.receiver
124
+
125
+ if node.receiver.is_a?(Prism::CallNode) && node.receiver.receiver
126
+ return get_first_receiver(node.receiver)
127
+ end
128
+
129
+ node.receiver
130
+ end
131
+ end
@@ -0,0 +1,52 @@
1
+ class RubyLsp::Ree::LocalVariablesParser
2
+ attr_reader :parsed_doc, :method_object
3
+
4
+ class LocalVariable
5
+ attr_reader :name
6
+
7
+ def initialize(name:)
8
+ @name = name
9
+ end
10
+ end
11
+
12
+ def initialize(method_obj)
13
+ @method_object = method_obj
14
+ end
15
+
16
+ def method_local_variables
17
+ method_body = method_object.method_body
18
+ return [] unless method_body
19
+
20
+ parse_body_local_variables(method_body)
21
+ end
22
+
23
+ private
24
+
25
+ def parse_body_local_variables(node_body)
26
+ local_variables = []
27
+
28
+ node_body.each do |node|
29
+ if node.is_a?(Prism::LocalVariableWriteNode)
30
+ local_variables << LocalVariable.new(name: node.name)
31
+ elsif node.is_a?(Prism::MultiWriteNode)
32
+ local_variables += node.lefts.map{ |x| LocalVariable.new(name: x.name) }
33
+ elsif node.respond_to?(:statements)
34
+ local_variables += parse_body_local_variables(node.statements.body)
35
+ elsif node.respond_to?(:block) && node.block && node.block.is_a?(Prism::BlockNode)
36
+ local_variables += parse_body_local_variables(get_method_body(node.block))
37
+ end
38
+ end
39
+
40
+ local_variables
41
+ end
42
+
43
+ def get_method_body(node)
44
+ return unless node.body
45
+
46
+ if node.body.is_a?(Prism::BeginNode)
47
+ node.body.statements.body
48
+ else
49
+ node.body.body
50
+ end
51
+ end
52
+ end
@@ -2,6 +2,8 @@ require_relative 'parsed_base_document'
2
2
  require_relative 'parsed_link_node'
3
3
  require_relative 'parsed_method_node'
4
4
  require_relative "../ree_constants"
5
+ require_relative "body_parsers/call_objects_parser"
6
+
5
7
  require 'ostruct'
6
8
 
7
9
  class RubyLsp::Ree::ParsedClassDocument < RubyLsp::Ree::ParsedBaseDocument
@@ -142,6 +144,7 @@ class RubyLsp::Ree::ParsedClassDocument < RubyLsp::Ree::ParsedBaseDocument
142
144
  end
143
145
 
144
146
  def parse_instance_methods
147
+ return if @doc_instance_methods
145
148
  @doc_instance_methods = []
146
149
 
147
150
  current_contract_node = nil
@@ -202,6 +205,10 @@ class RubyLsp::Ree::ParsedClassDocument < RubyLsp::Ree::ParsedBaseDocument
202
205
  .map(&:name)
203
206
  end
204
207
 
208
+ def parse_method_calls
209
+ RubyLsp::Ree::CallObjectsParser.new(self).class_call_objects
210
+ end
211
+
205
212
  def class_name
206
213
  class_node.constant_path.name.to_s
207
214
  end
@@ -1,4 +1,5 @@
1
1
  require 'prism'
2
+ require_relative "body_parsers/local_variables_parser"
2
3
 
3
4
  class RubyLsp::Ree::ParsedMethodNode
4
5
  attr_reader :method_node, :contract_node, :nested_local_methods
@@ -12,6 +13,13 @@ class RubyLsp::Ree::ParsedMethodNode
12
13
  @method_node.name
13
14
  end
14
15
 
16
+ def param_names
17
+ @method_node.parameters.requireds.map(&:name) +
18
+ @method_node.parameters.keywords.map(&:name) +
19
+ [@method_node.parameters.rest&.name] +
20
+ [@method_node.parameters.keyword_rest&.name]
21
+ end
22
+
15
23
  def has_contract?
16
24
  !!@contract_node
17
25
  end
@@ -24,6 +32,10 @@ class RubyLsp::Ree::ParsedMethodNode
24
32
  @method_node.location.end_line - 1
25
33
  end
26
34
 
35
+ def method_body
36
+ get_method_body(@method_node)
37
+ end
38
+
27
39
  def raised_errors_nested
28
40
  return @raised_errors_nested if @raised_errors_nested
29
41
  raised = raised_errors
@@ -94,6 +106,10 @@ class RubyLsp::Ree::ParsedMethodNode
94
106
  def contract_in_parentheses?
95
107
  @contract_node.opening == '(' && @contract_node.closing == ')'
96
108
  end
109
+
110
+ def parse_local_variables
111
+ RubyLsp::Ree::LocalVariablesParser.new(self).method_local_variables
112
+ end
97
113
 
98
114
  def parse_nested_local_methods(local_methods)
99
115
  unless @method_node.body
@@ -111,6 +127,20 @@ class RubyLsp::Ree::ParsedMethodNode
111
127
  @nested_local_methods.each{ _1.parse_nested_local_methods(local_methods) }
112
128
  end
113
129
 
130
+ def parse_call_objects
131
+ method_body = get_method_body(@method_node)
132
+ return [] unless method_body
133
+
134
+ call_nodes = parse_body_call_objects(method_body)
135
+ call_expressions = parse_body_call_expressions(method_body)
136
+
137
+ call_node_names = call_nodes.map(&:name) + call_expressions
138
+
139
+ call_node_names
140
+ end
141
+
142
+ private
143
+
114
144
  def parse_body_call_objects(node_body)
115
145
  call_nodes = []
116
146
 
@@ -121,6 +151,8 @@ class RubyLsp::Ree::ParsedMethodNode
121
151
  call_nodes += parse_body_call_objects(node.statements.body)
122
152
  elsif node.respond_to?(:block) && node.block && node.block.is_a?(Prism::BlockNode)
123
153
  call_nodes += parse_body_call_objects(get_method_body(node.block))
154
+ elsif node.respond_to?(:value) && node.value
155
+ call_nodes += parse_body_call_objects([node.value])
124
156
  end
125
157
  end
126
158
 
@@ -140,6 +172,8 @@ class RubyLsp::Ree::ParsedMethodNode
140
172
  end
141
173
 
142
174
  def get_method_body(node)
175
+ return unless node.body
176
+
143
177
  if node.body.is_a?(Prism::BeginNode)
144
178
  node.body.statements.body
145
179
  else
@@ -3,6 +3,7 @@ require_relative 'formatters/missing_error_definitions_formatter'
3
3
  require_relative 'formatters/missing_error_contracts_formatter'
4
4
  require_relative 'formatters/missing_error_locales_formatter'
5
5
  require_relative 'formatters/unused_links_formatter'
6
+ require_relative 'formatters/missing_imports_formatter'
6
7
 
7
8
  module RubyLsp
8
9
  module Ree
@@ -23,11 +24,12 @@ module RubyLsp
23
24
  source = document.source
24
25
 
25
26
  formatters = [
26
- RubyLsp::Ree::SortLinksFormatter,
27
27
  RubyLsp::Ree::MissingErrorDefinitionsFormatter,
28
28
  RubyLsp::Ree::MissingErrorContractsFormatter,
29
29
  RubyLsp::Ree::MissingErrorLocalesFormatter,
30
30
  RubyLsp::Ree::UnusedLinksFormatter,
31
+ RubyLsp::Ree::MissingImportsFormatter,
32
+ RubyLsp::Ree::SortLinksFormatter,
31
33
  ].select do |formatter|
32
34
  formatter_name = formatter.name.split('::').last.to_sym
33
35
  @settings[formatter_name] != false
@@ -1,3 +1,4 @@
1
+ require 'delegate'
1
2
  require_relative "utils/ree_lsp_utils"
2
3
 
3
4
  module RubyLsp
@@ -9,6 +10,20 @@ module RubyLsp
9
10
 
10
11
  REE_OBJECT_STRING = 'ree_object'
11
12
 
13
+ class ReeObjectDecorator < SimpleDelegator
14
+ include RubyLsp::Ree::ReeLspUtils
15
+
16
+ def object_package
17
+ return @package_name if @package_name
18
+
19
+ package_name_from_uri(uri)
20
+ end
21
+
22
+ def set_package!(package_name)
23
+ @package_name = package_name
24
+ end
25
+ end
26
+
12
27
  def initialize(index)
13
28
  @index = index
14
29
  end
@@ -41,6 +56,14 @@ module RubyLsp
41
56
  objects_by_name.detect{ _1.comments.to_s.lines.first&.chomp == REE_OBJECT_STRING }
42
57
  end
43
58
 
59
+ def find_objects(name)
60
+ objects_by_name = @index[name]
61
+ return unless objects_by_name
62
+
63
+ ree_objects = objects_by_name.select{ _1.comments.to_s.lines.first&.chomp == REE_OBJECT_STRING }
64
+ decorate_objects(ree_objects)
65
+ end
66
+
44
67
  def find_object_for_package(name, package_name)
45
68
  objects_by_name = @index[name]
46
69
  return unless objects_by_name
@@ -65,6 +88,12 @@ module RubyLsp
65
88
  def object_documentation(ree_object)
66
89
  ree_object.comments.lines[2..-1].join("\n").chomp
67
90
  end
91
+
92
+ private
93
+
94
+ def decorate_objects(ree_objects)
95
+ ree_objects.map{ ReeObjectDecorator.new(_1) }
96
+ end
68
97
  end
69
98
  end
70
99
  end
@@ -63,6 +63,35 @@ module RubyLsp
63
63
  source_lines[i] = ''
64
64
  end
65
65
  end
66
+
67
+ def add_links(parsed_doc, ree_objects, current_package)
68
+ new_text = ''
69
+
70
+ ree_objects.each do |ree_object|
71
+ link_text = if current_package == ree_object.object_package
72
+ "\s\slink :#{ree_object.name}"
73
+ else
74
+ package_str = ree_object.object_package == 'FILL_PACKAGE' ? 'FILL_PACKAGE' : ":#{ree_object.object_package}"
75
+ "\s\slink :#{ree_object.name}, from: #{package_str}"
76
+ end
77
+
78
+ if parsed_doc.links_container_node
79
+ link_text = "\s\s" + link_text
80
+ end
81
+
82
+ new_text += "\n" + link_text
83
+ end
84
+
85
+ new_text += "\n"
86
+
87
+ if parsed_doc.has_blank_links_container?
88
+ new_text = "\sdo#{new_text}\s\send\n"
89
+ end
90
+
91
+ line = parsed_doc.links_container_node.location.start_line - 1
92
+
93
+ source_lines[line] = source_lines[line].chomp + new_text
94
+ end
66
95
  end
67
96
  end
68
97
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyLsp
4
4
  module Ree
5
- VERSION = "0.1.15"
5
+ VERSION = "0.1.17"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp-ree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.15
4
+ version: 0.1.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ruslan Gatiyatov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-04-18 00:00:00.000000000 Z
11
+ date: 2025-04-28 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A Ruby LSP addon that adds extra editor functionality for Ree applications
14
14
  email:
@@ -33,6 +33,7 @@ files:
33
33
  - lib/ruby_lsp/ruby_lsp_ree/formatters/missing_error_contracts_formatter.rb
34
34
  - lib/ruby_lsp/ruby_lsp_ree/formatters/missing_error_definitions_formatter.rb
35
35
  - lib/ruby_lsp/ruby_lsp_ree/formatters/missing_error_locales_formatter.rb
36
+ - lib/ruby_lsp/ruby_lsp_ree/formatters/missing_imports_formatter.rb
36
37
  - lib/ruby_lsp/ruby_lsp_ree/formatters/sort_links_formatter.rb
37
38
  - lib/ruby_lsp/ruby_lsp_ree/formatters/unused_links_formatter.rb
38
39
  - lib/ruby_lsp/ruby_lsp_ree/handlers/completion_handler.rb
@@ -41,6 +42,8 @@ files:
41
42
  - lib/ruby_lsp/ruby_lsp_ree/listeners/completion_listener.rb
42
43
  - lib/ruby_lsp/ruby_lsp_ree/listeners/definition_listener.rb
43
44
  - lib/ruby_lsp/ruby_lsp_ree/listeners/hover_listener.rb
45
+ - lib/ruby_lsp/ruby_lsp_ree/parsing/body_parsers/call_objects_parser.rb
46
+ - lib/ruby_lsp/ruby_lsp_ree/parsing/body_parsers/local_variables_parser.rb
44
47
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_base_document.rb
45
48
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_class_document.rb
46
49
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_document_builder.rb