orbacle 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -28,18 +28,44 @@ module Orbacle
28
28
  case result
29
29
  when FindDefinitionUnderPosition::ConstantResult
30
30
  constants = @state.solve_reference2(result.const_ref)
31
- constants.map(&:location)
31
+ definitions_locations(constants)
32
32
  when FindDefinitionUnderPosition::MessageResult
33
33
  caller_type = get_type_of_caller_from_message_send(file_path, result.position_range)
34
34
  methods_definitions = get_methods_definitions_for_type(caller_type, result.name)
35
35
  methods_definitions = @state.get_methods(result.name) if methods_definitions.empty?
36
- methods_definitions.map(&:location).compact
36
+ definitions_locations(methods_definitions)
37
+ when FindDefinitionUnderPosition::SuperResult
38
+ method_surrounding_super = @state.find_method_including_position(file_path, result.keyword_position_range.start)
39
+ return [] if method_surrounding_super.nil?
40
+ super_method = @state.find_super_method(method_surrounding_super.id)
41
+ return definitions_locations(@state.get_methods(method_surrounding_super.name) - [method_surrounding_super]) if super_method.nil?
42
+ definitions_locations([super_method])
43
+ end
44
+ end
45
+
46
+ def completions_for_call_under_position(file_content, position)
47
+ result = FindCallUnderPosition.new(RubyParser.new).process_file(file_content, position)
48
+ case result
49
+ when FindCallUnderPosition::SelfResult
50
+ filtered_methods_from_class_name(result.nesting.to_scope.to_const_name.to_string, result.message_name)
51
+ when FindCallUnderPosition::IvarResult
52
+ ivar_node = @graph.get_ivar_definition_node(result.nesting.to_scope, result.ivar_name)
53
+ ivar_type = @state.type_of(ivar_node)
54
+ methods = []
55
+ ivar_type.each_possible_type do |type|
56
+ methods.concat(filtered_methods_from_class_name(type.name, result.message_name))
57
+ end
58
+ methods
37
59
  else
38
60
  []
39
61
  end
40
62
  end
41
63
 
42
64
  private
65
+ def definitions_locations(collection)
66
+ collection.map(&:location).compact
67
+ end
68
+
43
69
  def get_type_of_caller_from_message_send(file_path, position_range)
44
70
  message_send = @worklist
45
71
  .message_sends
@@ -50,9 +76,9 @@ module Orbacle
50
76
  def get_methods_definitions_for_type(type, method_name)
51
77
  case type
52
78
  when NominalType
53
- @state.get_instance_methods_from_class_name(type.name, method_name)
79
+ @state.get_deep_instance_methods_from_class_name(type.name, method_name)
54
80
  when ClassType
55
- @state.get_class_methods_from_class_name(type.name, method_name)
81
+ @state.get_deep_class_methods_from_class_name(type.name, method_name)
56
82
  when UnionType
57
83
  type.types_set.flat_map {|t| get_methods_definitions_for_type(t, method_name) }
58
84
  else
@@ -70,5 +96,13 @@ module Orbacle
70
96
  def pretty_print_type(type)
71
97
  TypePrettyPrinter.new.(type)
72
98
  end
99
+
100
+ def filtered_methods_from_class_name(class_name, message_name)
101
+ all_methods = @state.get_all_instance_methods_from_class_name(class_name)
102
+ starting_with = all_methods.select do |metod|
103
+ metod.name.to_s.start_with?(message_name.to_s)
104
+ end
105
+ starting_with.map(&:name).map(&:to_s)
106
+ end
73
107
  end
74
108
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'parser'
4
+
5
+ module Orbacle
6
+ class FindCallUnderPosition < Parser::AST::Processor
7
+ include AstUtils
8
+
9
+ SelfResult = Struct.new(:message_name, :nesting)
10
+ IvarResult = Struct.new(:message_name, :ivar_name, :nesting)
11
+
12
+ def initialize(parser)
13
+ @parser = parser
14
+ end
15
+
16
+ def process_file(file_content, searched_position)
17
+ ast = parser.parse(file_content)
18
+
19
+ @current_nesting = Nesting.empty
20
+ @searched_position = searched_position
21
+
22
+ process(ast)
23
+
24
+ @result
25
+ end
26
+
27
+ attr_reader :parser
28
+
29
+ def on_send(ast)
30
+ if ast.loc.selector && build_position_range_from_parser_range(ast.loc.selector).include_position?(@searched_position)
31
+ message_name = ast.children.fetch(1)
32
+ selector_position_range = build_position_range_from_parser_range(ast.loc.selector)
33
+ @result = if ast.children[0] == nil
34
+ SelfResult.new(message_name, @current_nesting)
35
+ else
36
+ case ast.children[0].type
37
+ when :self
38
+ SelfResult.new(message_name, @current_nesting)
39
+ when :ivar
40
+ IvarResult.new(message_name, ast.children[0].children[0], @current_nesting)
41
+ else
42
+ end
43
+ end
44
+ else
45
+ super
46
+ end
47
+ nil
48
+ end
49
+
50
+ def on_class(ast)
51
+ klass_name_ast, _ = ast.children
52
+ klass_name_ref = ConstRef.from_ast(klass_name_ast, @current_nesting)
53
+ with_new_nesting(@current_nesting.increase_nesting_const(klass_name_ref)) do
54
+ super
55
+ end
56
+ nil
57
+ end
58
+
59
+ def on_module(ast)
60
+ module_name_ast, _ = ast.children
61
+ module_name_ref = ConstRef.from_ast(module_name_ast, @current_nesting)
62
+ with_new_nesting(@current_nesting.increase_nesting_const(module_name_ref)) do
63
+ super
64
+ end
65
+ nil
66
+ end
67
+
68
+ def with_new_nesting(new_nesting)
69
+ previous_nesting = @current_nesting
70
+ @current_nesting = new_nesting
71
+ yield
72
+ @current_nesting = previous_nesting
73
+ end
74
+ end
75
+ end
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'parser'
4
+
3
5
  module Orbacle
4
6
  class FindDefinitionUnderPosition < Parser::AST::Processor
5
7
  include AstUtils
6
8
 
7
9
  ConstantResult = Struct.new(:const_ref)
8
10
  MessageResult = Struct.new(:name, :position_range)
11
+ SuperResult = Struct.new(:keyword_position_range)
9
12
 
10
13
  def initialize(parser)
11
14
  @parser = parser
@@ -57,15 +60,46 @@ module Orbacle
57
60
 
58
61
  def on_send(ast)
59
62
  if ast.loc.selector && build_position_range_from_parser_range(ast.loc.selector).include_position?(@searched_position)
60
- message_name = ast.children.fetch(1).to_s
61
- selector_position_range = build_position_range_from_parser_range(ast.loc.selector)
62
- @result = MessageResult.new(message_name, selector_position_range)
63
+ message_name = ast.children.fetch(1)
64
+ if message_name.equal?(:[])
65
+ selector_position_range = build_position_range_from_parser_range(ast.loc.selector)
66
+ if selector_position_range.on_edges?(@searched_position)
67
+ @result = MessageResult.new(message_name, selector_position_range)
68
+ else
69
+ super
70
+ end
71
+ else
72
+ selector_position_range = build_position_range_from_parser_range(ast.loc.selector)
73
+ @result = MessageResult.new(message_name, selector_position_range)
74
+ end
75
+ elsif ast.loc.dot && build_position_range_from_parser_range(ast.loc.dot).include_position?(@searched_position)
76
+ message_name = ast.children.fetch(1)
77
+ dot_position_range = build_position_range_from_parser_range(ast.loc.dot)
78
+ @result = MessageResult.new(message_name, dot_position_range)
63
79
  else
64
80
  super
65
81
  end
66
82
  nil
67
83
  end
68
84
 
85
+ def on_super(ast)
86
+ keyword_position_range = build_position_range_from_parser_range(ast.loc.keyword)
87
+ if keyword_position_range.include_position?(@searched_position)
88
+ @result = SuperResult.new(keyword_position_range)
89
+ else
90
+ super
91
+ end
92
+ nil
93
+ end
94
+
95
+ def on_zsuper(ast)
96
+ keyword_position_range = build_position_range_from_parser_range(ast.loc.keyword)
97
+ if keyword_position_range.include_position?(@searched_position)
98
+ @result = SuperResult.new(keyword_position_range)
99
+ end
100
+ nil
101
+ end
102
+
69
103
  def with_new_nesting(new_nesting)
70
104
  previous_nesting = @current_nesting
71
105
  @current_nesting = new_nesting
@@ -105,7 +105,7 @@ module Orbacle
105
105
  metod = Method.new(id, place_of_definition_id, name, location, visibility, args)
106
106
  @methods_by_class_id[metod.place_of_definition_id][metod.name] << metod
107
107
  @methods_by_id[metod.id] = metod
108
- return metod
108
+ metod
109
109
  end
110
110
 
111
111
  def find_instance_method_from_class_name(class_name, method_name)
@@ -133,6 +133,12 @@ module Orbacle
133
133
  get_instance_methods_from_class_id(eigenclass.id, method_name)
134
134
  end
135
135
 
136
+ def get_all_instance_methods_from_class_name(class_name)
137
+ klass = find_class_by_name(class_name)
138
+ return [] if klass.nil?
139
+ @methods_by_class_id[klass.id].values.flatten
140
+ end
141
+
136
142
  def find_class_method_from_class_name(class_name, method_name)
137
143
  get_class_methods_from_class_name(class_name, method_name).first
138
144
  end
@@ -158,15 +164,25 @@ module Orbacle
158
164
  end
159
165
  end
160
166
 
167
+ def find_method_including_position(file_path, position)
168
+ @methods_by_id
169
+ .values
170
+ .select {|m| m.location &&
171
+ m.location.uri.eql?(file_path) &&
172
+ m.location.position_range.include_position?(position) }
173
+ .sort_by {|m| m.location.span }
174
+ .first
175
+ end
176
+
161
177
  ### Definitions
162
178
 
163
- def add_klass(parent_ref)
179
+ def add_class(parent_ref)
164
180
  klass = Klass.new(id_generator.call, parent_ref)
165
181
  @classes_by_id[klass.id] = klass
166
182
  klass
167
183
  end
168
184
 
169
- def add_mod
185
+ def add_module
170
186
  mod = Mod.new(id_generator.call)
171
187
  @modules_by_id[mod.id] = mod
172
188
  mod
@@ -189,7 +205,7 @@ module Orbacle
189
205
  if definition.eigenclass_id
190
206
  get_class(definition.eigenclass_id)
191
207
  else
192
- eigenclass = add_klass(nil)
208
+ eigenclass = add_class(nil)
193
209
  definition.eigenclass_id = eigenclass.id
194
210
  eigenclass
195
211
  end
@@ -199,7 +215,7 @@ module Orbacle
199
215
 
200
216
  def add_constant(constant)
201
217
  @constants.add_element(constant.scope, constant.name, constant)
202
- return constant
218
+ constant
203
219
  end
204
220
 
205
221
  def solve_reference(const_ref)
@@ -260,7 +276,43 @@ module Orbacle
260
276
 
261
277
  def find_constant_for_definition(definition_id)
262
278
  @constants.find do |constant|
263
- constant.definition_id == definition_id
279
+ constant.definition_id.equal?(definition_id)
280
+ end
281
+ end
282
+
283
+ def find_deep_instance_method_from_class_name(class_name, method_name)
284
+ get_deep_instance_methods_from_class_name(class_name, method_name).first
285
+ end
286
+
287
+ def get_deep_instance_methods_from_class_name(class_name, method_name)
288
+ found_methods = get_instance_methods_from_class_name(class_name, method_name)
289
+ if found_methods.empty?
290
+ parent_name = get_parent_of(class_name)
291
+ if parent_name
292
+ get_deep_instance_methods_from_class_name(parent_name, method_name)
293
+ else
294
+ []
295
+ end
296
+ else
297
+ found_methods
298
+ end
299
+ end
300
+
301
+ def find_deep_class_method_from_class_name(class_name, method_name)
302
+ get_deep_class_methods_from_class_name(class_name, method_name).first
303
+ end
304
+
305
+ def get_deep_class_methods_from_class_name(class_name, method_name)
306
+ found_methods = get_class_methods_from_class_name(class_name, method_name)
307
+ if found_methods.empty?
308
+ parent_name = get_parent_of(class_name)
309
+ if parent_name
310
+ get_deep_class_methods_from_class_name(parent_name, method_name)
311
+ else
312
+ []
313
+ end
314
+ else
315
+ found_methods
264
316
  end
265
317
  end
266
318
 
data/lib/orbacle/graph.rb CHANGED
@@ -6,7 +6,8 @@ module Orbacle
6
6
  class Graph
7
7
  ZSuper = Struct.new(:send_result, :block)
8
8
  Yield = Struct.new(:send_args, :send_result)
9
- Metod = Struct.new(:args, :result, :yields, :zsupers)
9
+ Metod = Struct.new(:args, :result, :yields, :zsupers, :caller_node)
10
+ MetodGraph = Struct.new(:args, :result, :yields, :caller_node, :all_nodes, :all_edges)
10
11
  Lambda = Struct.new(:args, :result)
11
12
 
12
13
  def initialize
@@ -99,17 +100,33 @@ module Orbacle
99
100
  return cvariables[scope.absolute_str][ivar_name]
100
101
  end
101
102
 
102
- def get_metod_nodes(metod_id)
103
- return metods[metod_id]
103
+ def get_metod_nodes(method_id)
104
+ metod = metods[method_id]
105
+ if metod.instance_of?(Metod)
106
+ metod
107
+ else
108
+ new_nodes = metod.all_nodes.map(&:clone)
109
+ mapping = metod.all_nodes.zip(new_nodes).to_h
110
+ new_nodes.each(&method(:add_vertex))
111
+ metod.all_edges.each do |v1, v2|
112
+ add_edge(mapping.fetch(v1), mapping.fetch(v2))
113
+ end
114
+ new_arguments_nodes = metod.args.each_with_object({}) do |(k, v), h|
115
+ h[k] = mapping.fetch(v)
116
+ end
117
+ new_yields = metod.yields.map {|y| Yield.new(y.send_args.map {|a| mapping.fetch(a) }, mapping.fetch(y.send_result)) }
118
+ Metod.new(new_arguments_nodes, mapping.fetch(metod.result), new_yields, [], mapping.fetch(metod.caller_node))
119
+ end
104
120
  end
105
121
 
106
122
  def store_metod_nodes(metod_id, arguments_nodes)
107
123
  raise if !arguments_nodes.is_a?(Hash)
108
- metods[metod_id] ||= Metod.new(
109
- arguments_nodes,
110
- add_vertex(Node.new(:method_result, {})),
111
- [],
112
- [])
124
+ metods[metod_id] ||= Metod.new(arguments_nodes, add_vertex(Node.new(:method_result, {})), [], [], nil)
125
+ end
126
+
127
+ def store_metod_subgraph(metod_id, arguments_nodes, caller_node, result_node, yields, all_nodes, all_edges)
128
+ raise if !arguments_nodes.is_a?(Hash)
129
+ metods[metod_id] ||= MetodGraph.new(arguments_nodes, result_node, yields, caller_node, all_nodes, all_edges)
113
130
  end
114
131
 
115
132
  def get_lambda_nodes(lambda_id)
@@ -7,9 +7,14 @@ module Orbacle
7
7
  class LangServer
8
8
  include Lsp::LanguageServer
9
9
 
10
+ module Errors
11
+ NoDefinitionFound = Lsp::ResponseError::Base.new(1101, "No definition found under cursor position", nil)
12
+ end
13
+
10
14
  def initialize(logger, engine)
11
15
  @logger = logger
12
16
  @engine = engine
17
+ @file_contents = {}
13
18
  end
14
19
 
15
20
  attr_reader :logger, :engine
@@ -39,11 +44,25 @@ module Orbacle
39
44
  if locations
40
45
  Lsp::ResponseMessage.successful(locations.map(&method(:location_to_lsp_location)))
41
46
  else
42
- Lsp::ResponseMessage.successful(nil)
47
+ Lsp::ResponseMessage.new(nil, Errors::NoDefinitionFound)
43
48
  end
44
49
  end
45
50
  end
46
51
 
52
+ def handle_text_document_completion(request)
53
+ log_errors do
54
+ file_content = get_file_content(request.text_document.uri)
55
+ completions = engine.completions_for_call_under_position(file_content, Position.new(request.position.line, request.position.character - 1))
56
+ Lsp::ResponseMessage.successful(completions.map {|c| Lsp::CompletionItem.new(c) })
57
+ end
58
+ end
59
+
60
+ def handle_text_document_did_change(request)
61
+ log_errors do
62
+ @file_contents[request.text_document.uri.path] = request.content_changes[0].text
63
+ end
64
+ end
65
+
47
66
  def log_errors
48
67
  begin
49
68
  yield
@@ -53,6 +72,10 @@ module Orbacle
53
72
  end
54
73
  end
55
74
 
75
+ def get_file_content(uri)
76
+ @file_contents[uri.path] || File.read(uri.path)
77
+ end
78
+
56
79
  def location_to_lsp_location(location)
57
80
  Lsp::Location.new(
58
81
  URI("file://#{location.uri}"),
@@ -45,10 +45,6 @@ module Orbacle
45
45
 
46
46
  attr_reader :levels
47
47
 
48
- def to_primitive
49
- levels.map {|level| level.full_name }
50
- end
51
-
52
48
  def increase_nesting_const(const_ref)
53
49
  Nesting.new(levels + [ConstLevel.new(const_ref)])
54
50
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'parser/current'
3
+ require 'parser/ruby25'
4
4
 
5
5
  module Orbacle
6
6
  class RubyParser
@@ -8,8 +8,18 @@ module Orbacle
8
8
  SyntaxError = Class.new(Error)
9
9
  EncodingError = Class.new(Error)
10
10
 
11
+ def initialize
12
+ @my_parser = Class.new(Parser::Ruby25) do
13
+ def self.default_parser
14
+ my_parser = super()
15
+ my_parser.diagnostics.consumer = nil
16
+ my_parser
17
+ end
18
+ end
19
+ end
20
+
11
21
  def parse(content)
12
- Parser::CurrentRuby.parse(content)
22
+ @my_parser.parse(content)
13
23
  rescue Parser::SyntaxError
14
24
  raise SyntaxError
15
25
  rescue ::EncodingError