katakata_irb 0.1.11 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 810109ee76180f61c9b3672a8def67d0a32d53ca23b2436c65575457b586bf68
4
- data.tar.gz: e17ff5c4e226d785031f2b433b35a8763a49e6a254420fcacf96507975dfa70b
3
+ metadata.gz: 46f9fc2dd4b85529dff8a278f183954e48d7c7386944672d588e09315f6151ef
4
+ data.tar.gz: ae0dbe97feb4f1150f16bd061ea346ac2ff45a19eb1d38be4b6ffa25941418ce
5
5
  SHA512:
6
- metadata.gz: 47f0ea76bdf5a5599d219524346acca935b67d1ea73ed60d55887534bce442fffc8c483590b53cd84754c754b829551d837c38687282c497f2924c3ec616f0ff
7
- data.tar.gz: f3aec83bbee8f2e889fdcf31eecc85627972511bf1673894d7f45f8a6834e6b52212ec4beb1814e0e8ddd705cf39eb9370ba78ff395d288caadcaa07008ebed8
6
+ metadata.gz: a0306fd41b676ba4b65d3e739704f258950f7406deb4022455c11b4a1b6d42e98d5dceeb5a51e464e1fba36b42705ae6a87b237a2cd24d206f132ca9baa26125
7
+ data.tar.gz: 6eddf48f22628cd2ce4bb44e56be931b137f68b4d3194e374600c8e420eb2c84765021c5678bb30cabc0e8ce5f4229d0993c6cfa8502e5aaead8274cf4fd156c
data/Gemfile CHANGED
@@ -8,3 +8,4 @@ gemspec
8
8
  gem "rake", "~> 13.0"
9
9
 
10
10
  gem "minitest", "~> 5.0"
11
+ gem "simplecov"
data/katakata_irb.gemspec CHANGED
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
31
31
  # Uncomment to register a new dependency of your gem
32
32
  spec.add_dependency 'irb', '>= 1.4.0'
33
33
  spec.add_dependency 'reline', '>= 0.3.0'
34
+ spec.add_dependency 'prism', '>= 0.13.0'
34
35
  spec.add_dependency 'rbs'
35
36
 
36
37
  # For more information and examples about making a new gem, check out our
@@ -1,65 +1,74 @@
1
- require_relative 'nesting_parser'
1
+ # frozen_string_literal: true
2
+
2
3
  require_relative 'type_simulator'
3
4
  require 'rbs'
4
5
  require 'rbs/cli'
5
6
  require 'irb'
7
+ require 'prism'
6
8
 
7
9
  module KatakataIrb::Completor
8
- using KatakataIrb::TypeSimulator::LexerElemMatcher
9
10
  HIDDEN_METHODS = %w[Namespace TypeName] # defined by rbs, should be hidden
10
11
  singleton_class.attr_accessor :prev_analyze_result
11
12
 
13
+ def self.candidates_from_result(result)
14
+ candidates = case result
15
+ in [:require | :require_relative => method, name]
16
+ if IRB.const_defined? :RegexpCompletor # IRB::VERSION >= 1.8.2
17
+ path_completor = IRB::RegexpCompletor.new
18
+ elsif IRB.const_defined? :InputCompletor # IRB::VERSION <= 1.8.1
19
+ path_completor = IRB::InputCompletor
20
+ end
21
+ if !path_completor
22
+ []
23
+ elsif method == :require
24
+ path_completor.retrieve_files_to_require_from_load_path
25
+ else
26
+ path_completor.retrieve_files_to_require_relative_from_current_dir
27
+ end
28
+ in [:call_or_const, type, name, self_call]
29
+ ((self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS) | type.constants
30
+ in [:const, type, name, scope]
31
+ if type
32
+ scope_constants = type.types.flat_map do |t|
33
+ scope.table_module_constants(t.module_or_class) if t.is_a?(KatakataIrb::Types::SingletonType)
34
+ end
35
+ (scope_constants.compact | type.constants.map(&:to_s)).sort
36
+ else
37
+ scope.constants.sort
38
+ end
39
+ in [:ivar, name, scope]
40
+ ivars = scope.instance_variables.sort
41
+ name == '@' ? ivars + scope.class_variables.sort : ivars
42
+ in [:cvar, name, scope]
43
+ scope.class_variables
44
+ in [:gvar, name, scope]
45
+ scope.global_variables
46
+ in [:symbol, name]
47
+ Symbol.all_symbols.map { _1.inspect[1..] }
48
+ in [:call, type, name, self_call]
49
+ (self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS
50
+ in [:lvar_or_method, name, scope]
51
+ scope.self_type.all_methods.map(&:to_s) | scope.local_variables
52
+ else
53
+ []
54
+ end
55
+ [name || '', candidates]
56
+ end
57
+
12
58
  def self.setup
13
59
  KatakataIrb::Types.preload_in_thread
14
- completion_proc = ->(target, preposing = nil, postposing = nil) do
60
+ completion_proc = ->(preposing, target, _postposing, bind:) do
15
61
  verbose, $VERBOSE = $VERBOSE, nil
16
62
  code = "#{preposing}#{target}"
17
- irb_context = IRB.conf[:MAIN_CONTEXT]
18
- binding = irb_context.workspace.binding
19
- result = analyze code, binding
63
+ result = analyze code, bind
20
64
  KatakataIrb::Completor.prev_analyze_result = result
21
- candidates = case result
22
- in [:require | :require_relative => method, name]
23
- if IRB.const_defined? :InputCompletor # IRB::VERSION <= 1.8.1
24
- path_completor = IRB::InputCompletor
25
- elsif IRB.const_defined? :RegexpCompletor # IRB::VERSION >= 1.8.2
26
- path_completor = IRB::RegexpCompletor.new
27
- end
28
- if !path_completor
29
- []
30
- elsif method == :require
31
- path_completor.retrieve_files_to_require_from_load_path
32
- else
33
- path_completor.retrieve_files_to_require_relative_from_current_dir
34
- end
35
- in [:call_or_const, type, name, self_call]
36
- ((self_call ? type.all_methods: type.methods).map(&:to_s) - HIDDEN_METHODS) | type.constants
37
- in [:const, type, name]
38
- type.constants
39
- in [:ivar, name, *_scope]
40
- # TODO: scope
41
- ivars = binding.eval('self').instance_variables rescue []
42
- cvars = (binding.eval('self').class_variables rescue nil) if name == '@'
43
- ivars | (cvars || [])
44
- in [:cvar, name, *_scope]
45
- # TODO: scope
46
- binding.eval('self').class_variables rescue []
47
- in [:gvar, name]
48
- global_variables
49
- in [:symbol, name]
50
- Symbol.all_symbols.map { _1.inspect[1..] }
51
- in [:call, type, name, self_call]
52
- (self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS
53
- in [:lvar_or_method, name, scope]
54
- scope.self_type.all_methods.map(&:to_s) | scope.local_variables
55
- else
56
- []
57
- end
65
+ name, candidates = candidates_from_result(result).dup
66
+
58
67
  all_symbols_pattern = /\A[ -\/:-@\[-`\{-~]*\z/
59
68
  candidates.map(&:to_s).select { !_1.match?(all_symbols_pattern) && _1.start_with?(name) }.uniq.sort.map do
60
69
  target + _1[name.size..]
61
70
  end
62
- rescue => e
71
+ rescue SyntaxError, StandardError => e
63
72
  KatakataIrb.last_completion_error = e
64
73
  KatakataIrb.log_puts
65
74
  KatakataIrb.log_puts "#{e.inspect} stored to KatakataIrb.last_completion_error"
@@ -72,9 +81,9 @@ module KatakataIrb::Completor
72
81
  name = input[/[a-zA-Z_0-9]+[!?=]?\z/]
73
82
  method_doc = -> type do
74
83
  type = type.types.find { _1.all_methods.include? name.to_sym }
75
- if type in KatakataIrb::Types::SingletonType
84
+ if type.is_a? KatakataIrb::Types::SingletonType
76
85
  "#{KatakataIrb::Types.class_name_of(type.module_or_class)}.#{name}"
77
- elsif type in KatakataIrb::Types::InstanceType
86
+ elsif type.is_a? KatakataIrb::Types::InstanceType
78
87
  "#{KatakataIrb::Types.class_name_of(type.klass)}##{name}"
79
88
  end
80
89
  end
@@ -90,10 +99,10 @@ module KatakataIrb::Completor
90
99
  case KatakataIrb::Completor.prev_analyze_result
91
100
  in [:call_or_const, type, _name, _self_call]
92
101
  call_or_const_doc.call type
93
- in [:const, type, _name]
102
+ in [:const, type, _name, scope]
94
103
  # when prev_analyze_result is const, current analyze result might be call
95
- call_or_const_doc.call type
96
- in [:gvar, _name]
104
+ call_or_const_doc.call type if type
105
+ in [:gvar, _name, _scope]
97
106
  name
98
107
  in [:call, type, _name, _self_call]
99
108
  method_doc.call type
@@ -103,9 +112,19 @@ module KatakataIrb::Completor
103
112
  end
104
113
  end
105
114
 
106
- if IRB.const_defined? :InputCompletor # IRB::VERSION <= 1.8.1
107
- IRB::InputCompletor::CompletionProc.define_singleton_method :call do |*args|
108
- completion_proc.call(*args)
115
+ if IRB.const_defined? :RegexpCompletor # IRB::VERSION >= 1.8.2
116
+ IRB::RegexpCompletor.class_eval do
117
+ define_method :completion_candidates do |preposing, target, postposing, bind:|
118
+ completion_proc.call(preposing, target, postposing, bind: bind)
119
+ end
120
+ define_method :doc_namespace do |_preposing, matched, _postposing, bind:|
121
+ doc_namespace_proc.call matched
122
+ end
123
+ end
124
+ elsif IRB.const_defined? :InputCompletor # IRB::VERSION <= 1.8.1
125
+ IRB::InputCompletor::CompletionProc.define_singleton_method :call do |target, preposing = '', postposing = ''|
126
+ bind = IRB.conf[:MAIN_CONTEXT].workspace.binding
127
+ completion_proc.call(preposing, target, postposing, bind: bind)
109
128
  end
110
129
  IRB::InputCompletor.singleton_class.prepend(
111
130
  Module.new do
@@ -115,15 +134,6 @@ module KatakataIrb::Completor
115
134
  end
116
135
  end
117
136
  )
118
- elsif IRB.const_defined? :RegexpCompletor # IRB::VERSION >= 1.8.2
119
- IRB::RegexpCompletor.class_eval do
120
- define_method :completion_candidates do |preposing, target, postposing, bind:|
121
- completion_proc.call(target, preposing, postposing)
122
- end
123
- define_method :doc_namespace do |_preposing, matched, _postposing, bind:|
124
- doc_namespace_proc.call matched
125
- end
126
- end
127
137
  else
128
138
  puts 'Cannot activate katakata_irb'
129
139
  end
@@ -174,179 +184,93 @@ module KatakataIrb::Completor
174
184
  Reline.add_dialog_proc(:show_type, type_dialog_proc, Reline::DEFAULT_DIALOG_CONTEXT)
175
185
  end
176
186
 
177
- def self.empty_binding()
178
- Kernel.binding
179
- end
187
+ def self.analyze(code, binding = Object::TOPLEVEL_BINDING)
188
+ # Workaround for https://github.com/ruby/prism/issues/1592
189
+ return if code.match?(/%[qQ]\z/)
180
190
 
181
- def self.analyze(code, binding = empty_binding)
182
191
  lvars_code = binding.local_variables.map do |name|
183
192
  "#{name}="
184
193
  end.join + "nil;\n"
185
194
  code = lvars_code + code
186
- tokens = KatakataIrb::NestingParser.tokenize code
187
- last_opens = KatakataIrb::NestingParser.open_tokens(tokens)
188
- closings = last_opens.map do |t|
189
- case t.tok
190
- when /\A%.?[<>]\z/
191
- $/ + '>'
192
- when '{', '#{', /\A%.?[{}]\z/
193
- $/ + '}'
194
- when '(', /\A%.?[()]\z/
195
- # do not insert \n before closing paren. workaround to avoid syntax error of "a in ^(b\n)"
196
- ')'
197
- when '[', /\A%.?[\[\]]\z/
198
- $/ + ']'
199
- when /\A%.?(.)\z/
200
- $1
201
- when '"', "'", '/', '`'
202
- t.tok
203
- when /\A<<[~-]?(?:"(?<s>.+)"|'(?<s>.+)'|(?<s>.+))/
204
- $/ + ($1 || $2 || $3) + $/
205
- when ':"', ":'", ':'
206
- t.tok[1]
207
- when '?'
208
- # ternary operator
209
- ' : value'
210
- when '|'
211
- # block args
212
- '|'
213
- else
214
- $/ + 'end'
215
- end
216
- end
217
- # remove error tokens
218
- tokens.pop while tokens&.last&.tok&.empty?
195
+ ast = Prism.parse(code).value
196
+ name = code[/(@@|@|\$)?\w*\z/]
197
+ *parents, target_node = find_target ast, code.bytesize - name.bytesize
198
+ return unless target_node
219
199
 
220
- case tokens.last
221
- in { event: :on_ignored_by_ripper, tok: '.' }
222
- suffix = 'method'
223
- name = ''
224
- in { dot: true }
225
- suffix = 'method'
226
- name = ''
227
- in { event: :on_symbeg }
228
- suffix = 'symbol'
229
- name = ''
230
- in { event: :on_ident | :on_kw, tok: }
231
- return unless code.delete_suffix! tok
232
- suffix = 'method'
233
- name = tok
234
- in { event: :on_const, tok: }
235
- return unless code.delete_suffix! tok
236
- suffix = 'Const'
237
- name = tok
238
- in { event: :on_tstring_content, tok: }
239
- return unless code.delete_suffix! tok
240
- suffix = 'string'
241
- name = tok.rstrip
242
- in { event: :on_gvar, tok: }
243
- return unless code.delete_suffix! tok
244
- suffix = '$gvar'
245
- name = tok
246
- in { event: :on_ivar, tok: }
247
- return unless code.delete_suffix! tok
248
- suffix = '@ivar'
249
- name = tok
250
- in { event: :on_cvar, tok: }
251
- return unless code.delete_suffix! tok
252
- suffix = '@@cvar'
253
- name = tok
254
- else
255
- return
256
- end
257
- sexp = Ripper.sexp code + suffix + closings.reverse.join
258
- lines = code.lines
259
- line_no = lines.size
260
- col = lines.last.bytesize
261
- if lines.last.end_with? "\n"
262
- line_no += 1
263
- col = 0
264
- end
200
+ calculate_scope = -> { KatakataIrb::TypeSimulator.calculate_target_type_scope(binding, parents, target_node).last }
201
+ calculate_type_scope = ->(node) { KatakataIrb::TypeSimulator.calculate_target_type_scope binding, [*parents, target_node], node }
265
202
 
266
- if sexp in [:program, [_lvars_exp, *rest_statements]]
267
- sexp = [:program, rest_statements]
203
+ if target_node.is_a?(Prism::StringNode) || target_node.is_a?(Prism::InterpolatedStringNode)
204
+ args_node = parents[-1]
205
+ call_node = parents[-2]
206
+ return unless args_node.is_a?(Prism::ArgumentsNode) && args_node.arguments.size == 1
207
+ return unless call_node.is_a?(Prism::CallNode) && call_node.receiver.nil? && (call_node.message == 'require' || call_node.message == 'require_relative')
208
+ return [call_node.message.to_sym, name.rstrip]
268
209
  end
269
210
 
270
- *parents, expression, target = find_target sexp, line_no, col
271
- in_class_module = parents&.any? { _1 in [:class | :module,] }
272
- icvar_available = !in_class_module
273
- return unless target in [_type, String, [Integer, Integer]]
274
- if target in [:@gvar,]
275
- return [:gvar, name]
276
- elsif target in [:@ivar,]
277
- return [:ivar, name] if icvar_available
278
- elsif target in [:@cvar,]
279
- return [:cvar, name] if icvar_available
280
- end
281
- return unless expression
282
- calculate_scope = -> { KatakataIrb::TypeSimulator.calculate_binding_scope binding, parents, expression }
283
- calculate_receiver = -> receiver { KatakataIrb::TypeSimulator.calculate_receiver binding, parents, receiver }
211
+ case target_node
212
+ when Prism::SymbolNode
213
+ if parents.last.is_a? Prism::BlockArgumentNode # method(&:target)
214
+ receiver_type, _scope = calculate_type_scope.call target_node
215
+ [:call, receiver_type, name, false]
216
+ else
217
+ [:symbol, name] unless name.empty?
218
+ end
219
+ when Prism::CallNode
220
+ return [:lvar_or_method, name, calculate_scope.call] if target_node.receiver.nil?
284
221
 
285
- if (target in [:@tstring_content,]) && (parents[-4] in [:command, [:@ident, 'require' | 'require_relative' => require_method,],])
286
- # `require 'target'`
287
- return [require_method.to_sym, name.rstrip]
288
- end
289
- if (target in [:@ident,]) && (expression in [:symbol,]) && (parents[-2] in [:args_add_block, Array => _args, [:symbol_literal, ^expression]])
290
- # `method(&:target)`
291
- receiver_ref = [:var_ref, [:@ident, '_1', [0, 0]]]
292
- block_statements = [receiver_ref]
293
- parents[-1] = parents[-2][-1] = [:brace_block, nil, block_statements]
294
- parents << block_statements
295
- return [:call, calculate_receiver.call(receiver_ref), name, false]
296
- end
297
- case expression
298
- in [:vcall | :var_ref, [:@ident,]]
222
+ self_call = target_node.receiver.is_a? Prism::SelfNode
223
+ op = target_node.call_operator
224
+ receiver_type, _scope = calculate_type_scope.call target_node.receiver
225
+ receiver_type = receiver_type.nonnillable if op == '&.'
226
+ [op == '::' ? :call_or_const : :call, receiver_type, name, self_call]
227
+ when Prism::LocalVariableReadNode, Prism::LocalVariableTargetNode
299
228
  [:lvar_or_method, name, calculate_scope.call]
300
- in [:symbol, [:@ident | :@const | :@op | :@kw,]]
301
- [:symbol, name] unless name.empty?
302
- in [:var_ref | :const_ref, [:@const,]]
303
- # TODO
304
- [:const, KatakataIrb::Types::SingletonType.new(Object), name]
305
- in [:var_ref, [:@gvar,]]
306
- [:gvar, name]
307
- in [:var_ref, [:@ivar,]]
308
- [:ivar, name, calculate_scope.call.self_type] if icvar_available
309
- in [:var_ref, [:@cvar,]]
310
- [:cvar, name, calculate_scope.call.self_type] if icvar_available
311
- in [:call, receiver, [:@op, '::',] | :'::', [:@ident | :@const,]]
312
- self_call = (receiver in [:var_ref, [:@kw, 'self',]])
313
- [:call_or_const, calculate_receiver.call(receiver), name, self_call]
314
- in [:call, receiver, [:@period,], [:@ident | :@const,]]
315
- self_call = (receiver in [:var_ref, [:@kw, 'self',]])
316
- [:call, calculate_receiver.call(receiver), name, self_call]
317
- in [:call, receiver, [:@op, '&.',], [:@ident | :@const,]]
318
- self_call = (receiver in [:var_ref, [:@kw, 'self',]])
319
- [:call, calculate_receiver.call(receiver).nonnillable, name, self_call]
320
- in [:const_path_ref, receiver, [:@const,]]
321
- [:const, calculate_receiver.call(receiver), name]
322
- in [:top_const_ref, [:@const,]]
323
- [:const, KatakataIrb::Types::SingletonType.new(Object), name]
324
- in [:def,] | [:string_content,] | [:field | :var_field | :const_path_field,] | [:defs,] | [:rest_param,] | [:kwrest_param,] | [:blockarg,] | [[:@ident,],]
325
- in [Array,] # `xstring`, /regexp/
326
- else
327
- KatakataIrb.log_puts
328
- KatakataIrb.log_puts [:UNIMPLEMENTED_EXPRESSION, expression].inspect
329
- KatakataIrb.log_puts
330
- nil
229
+ when Prism::ConstantReadNode, Prism::ConstantTargetNode
230
+ if parents.last.is_a? Prism::ConstantPathNode
231
+ path_node = parents.last
232
+ if path_node.parent # A::B
233
+ receiver, scope = calculate_type_scope.call(path_node.parent)
234
+ [:const, receiver, name, scope]
235
+ else # ::A
236
+ scope = calculate_scope.call
237
+ [:const, KatakataIrb::Types::SingletonType.new(Object), name, scope]
238
+ end
239
+ else
240
+ [:const, nil, name, calculate_scope.call]
241
+ end
242
+ when Prism::GlobalVariableReadNode, Prism::GlobalVariableTargetNode
243
+ [:gvar, name, calculate_scope.call]
244
+ when Prism::InstanceVariableReadNode, Prism::InstanceVariableTargetNode
245
+ [:ivar, name, calculate_scope.call]
246
+ when Prism::ClassVariableReadNode, Prism::ClassVariableTargetNode
247
+ [:cvar, name, calculate_scope.call]
331
248
  end
332
249
  end
333
250
 
334
- def self.find_target(sexp, line, col, stack = [sexp])
335
- return unless sexp.is_a? Array
336
- sexp.each do |child|
337
- case child
338
- in [Symbol, String, [Integer => l, Integer => c]]
339
- if l == line && c == col
340
- stack << child
341
- return stack
342
- end
343
- else
344
- stack << child
345
- result = find_target(child, line, col, stack)
346
- return result if result
347
- stack.pop
251
+ def self.find_target(node, position)
252
+ location = (
253
+ case node
254
+ when Prism::CallNode
255
+ node.message_loc
256
+ when Prism::SymbolNode
257
+ node.value_loc
258
+ when Prism::StringNode
259
+ node.content_loc
260
+ when Prism::InterpolatedStringNode
261
+ node.closing_loc if node.parts.empty?
348
262
  end
263
+ )
264
+ return [node] if location&.start_offset == position
265
+
266
+ node.child_nodes.each do |n|
267
+ next unless n.is_a? Prism::Node
268
+ match = find_target(n, position)
269
+ next unless match
270
+ match.unshift node
271
+ return match
349
272
  end
350
- nil
273
+
274
+ [node] if node.location.start_offset == position
351
275
  end
352
276
  end