katakata_irb 0.1.11 → 0.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: 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