katakata_irb 0.1.12 → 0.2.1

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: de309515e451688552ff4758e266ba13966dfef8e62601d0496e3dfc1df256c1
4
- data.tar.gz: 9923f034c376e28156f7d4a8552f893a2d70546372f7feea0f0f04e5072d782a
3
+ metadata.gz: 034055ed4a5fcf5bde6a1b86723c2963f82ace7c5b0411cd9a3e09fd734b10d2
4
+ data.tar.gz: 3e04438496f0241815cf7b674db57f7b644360520d9d829d75d5be61b63b78e5
5
5
  SHA512:
6
- metadata.gz: 956de3a2a11d2ab1d877dbc832010fe501c18d1708dc9371e3d7dec9b0a158c66a6a05456660336e72b321662cae4ef764f2f0e065c4f7eab2f542262f59fe43
7
- data.tar.gz: 2cc3a0e8829e6567fa34dd9a3218db03a8b51f5733392680b759810395aaab62ed2ca18c2fbc52f67c2608d6c0e6107c22e4546dcfc0c3aa8f055450ee8287f0
6
+ metadata.gz: 4b406ecd5c611cdc86220efa506c403dff220db4be5fb1bc1fab10eeb70931f0d7eaee8a45a702a6addb5de793e0c219562975d769fe31616305cd87fe4cfa4a
7
+ data.tar.gz: 147b09a36c68a66c917fff2157797d1124ec06e0333ee7cec26ef83b9d726c398bb83bbdce81f88c1706067ecc3f9bf99506d5706d3b22ccc3c8aa2ee6ce5cb1
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.14.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,72 @@
1
- require_relative 'nesting_parser'
2
- require_relative 'type_simulator'
3
- require 'rbs'
4
- require 'rbs/cli'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'type_analyzer'
5
4
  require 'irb'
5
+ require 'prism'
6
6
 
7
7
  module KatakataIrb::Completor
8
- using KatakataIrb::TypeSimulator::LexerElemMatcher
9
8
  HIDDEN_METHODS = %w[Namespace TypeName] # defined by rbs, should be hidden
10
9
  singleton_class.attr_accessor :prev_analyze_result
11
10
 
11
+ def self.candidates_from_result(result)
12
+ candidates = case result
13
+ in [:require | :require_relative => method, name]
14
+ if IRB.const_defined? :RegexpCompletor # IRB::VERSION >= 1.8.2
15
+ path_completor = IRB::RegexpCompletor.new
16
+ elsif IRB.const_defined? :InputCompletor # IRB::VERSION <= 1.8.1
17
+ path_completor = IRB::InputCompletor
18
+ end
19
+ if !path_completor
20
+ []
21
+ elsif method == :require
22
+ path_completor.retrieve_files_to_require_from_load_path
23
+ else
24
+ path_completor.retrieve_files_to_require_relative_from_current_dir
25
+ end
26
+ in [:call_or_const, type, name, self_call]
27
+ ((self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS) | type.constants
28
+ in [:const, type, name, scope]
29
+ if type
30
+ scope_constants = type.types.flat_map do |t|
31
+ scope.table_module_constants(t.module_or_class) if t.is_a?(KatakataIrb::Types::SingletonType)
32
+ end
33
+ (scope_constants.compact | type.constants.map(&:to_s)).sort
34
+ else
35
+ scope.constants.sort
36
+ end
37
+ in [:ivar, name, scope]
38
+ ivars = scope.instance_variables.sort
39
+ name == '@' ? ivars + scope.class_variables.sort : ivars
40
+ in [:cvar, name, scope]
41
+ scope.class_variables
42
+ in [:gvar, name, scope]
43
+ scope.global_variables
44
+ in [:symbol, name]
45
+ Symbol.all_symbols.map { _1.inspect[1..] }
46
+ in [:call, type, name, self_call]
47
+ (self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS
48
+ in [:lvar_or_method, name, scope]
49
+ scope.self_type.all_methods.map(&:to_s) | scope.local_variables
50
+ else
51
+ []
52
+ end
53
+ [name || '', candidates]
54
+ end
55
+
12
56
  def self.setup
13
57
  KatakataIrb::Types.preload_in_thread
14
- completion_proc = ->(target, preposing = nil, postposing = nil) do
58
+ completion_proc = ->(preposing, target, _postposing, bind:) do
15
59
  verbose, $VERBOSE = $VERBOSE, nil
16
60
  code = "#{preposing}#{target}"
17
- irb_context = IRB.conf[:MAIN_CONTEXT]
18
- binding = irb_context.workspace.binding
19
- result = analyze code, binding
61
+ result = analyze code, bind
20
62
  KatakataIrb::Completor.prev_analyze_result = result
21
- candidates = case result
22
- in [:require | :require_relative => method, name]
23
- if IRB.const_defined? :RegexpCompletor # IRB::VERSION >= 1.8.2
24
- path_completor = IRB::RegexpCompletor.new
25
- elsif IRB.const_defined? :InputCompletor # IRB::VERSION <= 1.8.1
26
- path_completor = IRB::InputCompletor
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
63
+ name, candidates = candidates_from_result(result).dup
64
+
58
65
  all_symbols_pattern = /\A[ -\/:-@\[-`\{-~]*\z/
59
66
  candidates.map(&:to_s).select { !_1.match?(all_symbols_pattern) && _1.start_with?(name) }.uniq.sort.map do
60
67
  target + _1[name.size..]
61
68
  end
62
- rescue => e
69
+ rescue SyntaxError, StandardError => e
63
70
  KatakataIrb.last_completion_error = e
64
71
  KatakataIrb.log_puts
65
72
  KatakataIrb.log_puts "#{e.inspect} stored to KatakataIrb.last_completion_error"
@@ -68,13 +75,13 @@ module KatakataIrb::Completor
68
75
  $VERBOSE = verbose
69
76
  end
70
77
 
71
- doc_namespace_proc = ->(input) do
72
- name = input[/[a-zA-Z_0-9]+[!?=]?\z/]
78
+ doc_namespace_proc = -> input do
79
+ name = input[/[a-zA-Z_0-9]*[!?=]?\z/]
73
80
  method_doc = -> type do
74
81
  type = type.types.find { _1.all_methods.include? name.to_sym }
75
- if type in KatakataIrb::Types::SingletonType
82
+ if type.is_a? KatakataIrb::Types::SingletonType
76
83
  "#{KatakataIrb::Types.class_name_of(type.module_or_class)}.#{name}"
77
- elsif type in KatakataIrb::Types::InstanceType
84
+ elsif type.is_a? KatakataIrb::Types::InstanceType
78
85
  "#{KatakataIrb::Types.class_name_of(type.klass)}##{name}"
79
86
  end
80
87
  end
@@ -87,18 +94,44 @@ module KatakataIrb::Completor
87
94
  end
88
95
  end
89
96
 
97
+ value_doc = -> type do
98
+ return unless type
99
+ type.types.each do |t|
100
+ case t
101
+ when KatakataIrb::Types::SingletonType
102
+ return KatakataIrb::Types.class_name_of(t.module_or_class)
103
+ when KatakataIrb::Types::InstanceType
104
+ return KatakataIrb::Types.class_name_of(t.klass)
105
+ when KatakataIrb::Types::ProcType
106
+ return 'Proc'
107
+ end
108
+ end
109
+ nil
110
+ end
111
+
90
112
  case KatakataIrb::Completor.prev_analyze_result
91
113
  in [:call_or_const, type, _name, _self_call]
92
114
  call_or_const_doc.call type
93
- in [:const, type, _name]
94
- # when prev_analyze_result is const, current analyze result might be call
95
- call_or_const_doc.call type
96
- in [:gvar, _name]
97
- name
115
+ in [:const, type, _name, scope]
116
+ if type
117
+ call_or_const_doc.call type
118
+ else
119
+ value_doc.call scope[name]
120
+ end
121
+ in [:gvar, _name, scope]
122
+ value_doc.call scope["$#{name}"]
123
+ in [:ivar, _name, scope]
124
+ value_doc.call scope["@#{name}"]
125
+ in [:cvar, _name, scope]
126
+ value_doc.call scope["@@#{name}"]
98
127
  in [:call, type, _name, _self_call]
99
128
  method_doc.call type
100
129
  in [:lvar_or_method, _name, scope]
101
- method_doc.call scope.self_type unless scope.local_variables.include?(name)
130
+ if scope.local_variables.include?(name)
131
+ value_doc.call scope[name]
132
+ else
133
+ method_doc.call scope.self_type
134
+ end
102
135
  else
103
136
  end
104
137
  end
@@ -106,15 +139,16 @@ module KatakataIrb::Completor
106
139
  if IRB.const_defined? :RegexpCompletor # IRB::VERSION >= 1.8.2
107
140
  IRB::RegexpCompletor.class_eval do
108
141
  define_method :completion_candidates do |preposing, target, postposing, bind:|
109
- completion_proc.call(target, preposing, postposing)
142
+ completion_proc.call(preposing, target, postposing, bind: bind)
110
143
  end
111
144
  define_method :doc_namespace do |_preposing, matched, _postposing, bind:|
112
145
  doc_namespace_proc.call matched
113
146
  end
114
147
  end
115
148
  elsif IRB.const_defined? :InputCompletor # IRB::VERSION <= 1.8.1
116
- IRB::InputCompletor::CompletionProc.define_singleton_method :call do |*args|
117
- completion_proc.call(*args)
149
+ IRB::InputCompletor::CompletionProc.define_singleton_method :call do |target, preposing = '', postposing = ''|
150
+ bind = IRB.conf[:MAIN_CONTEXT].workspace.binding
151
+ completion_proc.call(preposing, target, postposing, bind: bind)
118
152
  end
119
153
  IRB::InputCompletor.singleton_class.prepend(
120
154
  Module.new do
@@ -174,179 +208,93 @@ module KatakataIrb::Completor
174
208
  Reline.add_dialog_proc(:show_type, type_dialog_proc, Reline::DEFAULT_DIALOG_CONTEXT)
175
209
  end
176
210
 
177
- def self.empty_binding()
178
- Kernel.binding
179
- end
211
+ def self.analyze(code, binding = Object::TOPLEVEL_BINDING)
212
+ # Workaround for https://github.com/ruby/prism/issues/1592
213
+ return if code.match?(/%[qQ]\z/)
180
214
 
181
- def self.analyze(code, binding = empty_binding)
182
215
  lvars_code = binding.local_variables.map do |name|
183
216
  "#{name}="
184
217
  end.join + "nil;\n"
185
218
  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?
219
+ ast = Prism.parse(code).value
220
+ name = code[/(@@|@|\$)?\w*[!?=]?\z/]
221
+ *parents, target_node = find_target ast, code.bytesize - name.bytesize
222
+ return unless target_node
219
223
 
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
224
+ calculate_scope = -> { KatakataIrb::TypeAnalyzer.calculate_target_type_scope(binding, parents, target_node).last }
225
+ calculate_type_scope = ->(node) { KatakataIrb::TypeAnalyzer.calculate_target_type_scope binding, [*parents, target_node], node }
265
226
 
266
- if sexp in [:program, [_lvars_exp, *rest_statements]]
267
- sexp = [:program, rest_statements]
227
+ if target_node.is_a?(Prism::StringNode) || target_node.is_a?(Prism::InterpolatedStringNode)
228
+ args_node = parents[-1]
229
+ call_node = parents[-2]
230
+ return unless args_node.is_a?(Prism::ArgumentsNode) && args_node.arguments.size == 1
231
+ return unless call_node.is_a?(Prism::CallNode) && call_node.receiver.nil? && (call_node.message == 'require' || call_node.message == 'require_relative')
232
+ return [call_node.message.to_sym, name.rstrip]
268
233
  end
269
234
 
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 }
235
+ case target_node
236
+ when Prism::SymbolNode
237
+ if parents.last.is_a? Prism::BlockArgumentNode # method(&:target)
238
+ receiver_type, _scope = calculate_type_scope.call target_node
239
+ [:call, receiver_type, name, false]
240
+ else
241
+ [:symbol, name] unless name.empty?
242
+ end
243
+ when Prism::CallNode
244
+ return [:lvar_or_method, name, calculate_scope.call] if target_node.receiver.nil?
284
245
 
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,]]
246
+ self_call = target_node.receiver.is_a? Prism::SelfNode
247
+ op = target_node.call_operator
248
+ receiver_type, _scope = calculate_type_scope.call target_node.receiver
249
+ receiver_type = receiver_type.nonnillable if op == '&.'
250
+ [op == '::' ? :call_or_const : :call, receiver_type, name, self_call]
251
+ when Prism::LocalVariableReadNode, Prism::LocalVariableTargetNode
299
252
  [: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
253
+ when Prism::ConstantReadNode, Prism::ConstantTargetNode
254
+ if parents.last.is_a? Prism::ConstantPathNode
255
+ path_node = parents.last
256
+ if path_node.parent # A::B
257
+ receiver, scope = calculate_type_scope.call(path_node.parent)
258
+ [:const, receiver, name, scope]
259
+ else # ::A
260
+ scope = calculate_scope.call
261
+ [:const, KatakataIrb::Types::SingletonType.new(Object), name, scope]
262
+ end
263
+ else
264
+ [:const, nil, name, calculate_scope.call]
265
+ end
266
+ when Prism::GlobalVariableReadNode, Prism::GlobalVariableTargetNode
267
+ [:gvar, name, calculate_scope.call]
268
+ when Prism::InstanceVariableReadNode, Prism::InstanceVariableTargetNode
269
+ [:ivar, name, calculate_scope.call]
270
+ when Prism::ClassVariableReadNode, Prism::ClassVariableTargetNode
271
+ [:cvar, name, calculate_scope.call]
331
272
  end
332
273
  end
333
274
 
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
275
+ def self.find_target(node, position)
276
+ location = (
277
+ case node
278
+ when Prism::CallNode
279
+ node.message_loc
280
+ when Prism::SymbolNode
281
+ node.value_loc
282
+ when Prism::StringNode
283
+ node.content_loc
284
+ when Prism::InterpolatedStringNode
285
+ node.closing_loc if node.parts.empty?
348
286
  end
287
+ )
288
+ return [node] if location&.start_offset == position
289
+
290
+ node.child_nodes.each do |n|
291
+ next unless n.is_a? Prism::Node
292
+ match = find_target(n, position)
293
+ next unless match
294
+ match.unshift node
295
+ return match
349
296
  end
350
- nil
297
+
298
+ [node] if node.location.start_offset == position
351
299
  end
352
300
  end