katakata_irb 0.1.12 → 0.2.1

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: 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