rbs 3.0.4 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/rbs/cli.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require "open3"
4
4
  require "optparse"
5
5
  require "shellwords"
6
+ require "abbrev"
7
+ require "stringio"
6
8
 
7
9
  module RBS
8
10
  class CLI
@@ -88,7 +90,7 @@ module RBS
88
90
  @stderr = stderr
89
91
  end
90
92
 
91
- COMMANDS = [:ast, :annotate, :list, :ancestors, :methods, :method, :validate, :constant, :paths, :prototype, :vendor, :parse, :test, :collection]
93
+ COMMANDS = [:ast, :annotate, :list, :ancestors, :methods, :method, :validate, :constant, :paths, :prototype, :vendor, :parse, :test, :collection, :subtract]
92
94
 
93
95
  def parse_logging_options(opts)
94
96
  opts.on("--log-level LEVEL", "Specify log level (defaults to `warn`)") do |level|
@@ -450,7 +452,6 @@ Examples:
450
452
  EOU
451
453
 
452
454
  opts.on("--silent") do
453
- require "stringio"
454
455
  @stdout = StringIO.new
455
456
  end
456
457
  end.parse!(args)
@@ -920,6 +921,10 @@ Options:
920
921
  end
921
922
 
922
923
  def run_parse(args, options)
924
+ parse_method = :parse_signature
925
+ # @type var e_code: String?
926
+ e_code = nil
927
+
923
928
  OptionParser.new do |opts|
924
929
  opts.banner = <<-EOB
925
930
  Usage: rbs parse [files...]
@@ -929,22 +934,35 @@ Parse given RBS files and print syntax errors.
929
934
  Examples:
930
935
 
931
936
  $ rbs parse sig/app/models.rbs sig/app/controllers.rbs
937
+
938
+ Options:
932
939
  EOB
933
- end.parse!(args)
934
940
 
935
- loader = options.loader()
941
+ opts.on('-e CODE', 'One line RBS script to parse') { |e| e_code = e }
942
+ opts.on('--type', 'Parse code as a type') { |e| parse_method = :parse_type }
943
+ opts.on('--method-type', 'Parse code as a method type') { |e| parse_method = :parse_method_type }
944
+ end.parse!(args)
936
945
 
937
946
  syntax_error = false
938
- args.each do |path|
947
+ bufs = args.flat_map do |path|
939
948
  path = Pathname(path)
940
- loader.each_file(path, skip_hidden: false, immediate: true) do |file_path|
941
- RBS.logger.info "Parsing #{file_path}..."
942
- buffer = Buffer.new(content: file_path.read, name: file_path)
943
- Parser.parse_signature(buffer)
944
- rescue RBS::ParsingError => ex
945
- stdout.puts ex.message
946
- syntax_error = true
949
+ FileFinder.each_file(path, skip_hidden: false, immediate: true).map do |file_path|
950
+ Buffer.new(content: file_path.read, name: file_path)
951
+ end
952
+ end
953
+ bufs << Buffer.new(content: e_code, name: '-e') if e_code
954
+
955
+ bufs.each do |buf|
956
+ RBS.logger.info "Parsing #{buf.name}..."
957
+ case parse_method
958
+ when :parse_signature
959
+ Parser.parse_signature(buf)
960
+ else
961
+ Parser.public_send(parse_method, buf, require_eof: true)
947
962
  end
963
+ rescue RBS::ParsingError => ex
964
+ stdout.puts ex.message
965
+ syntax_error = true
948
966
  end
949
967
 
950
968
  exit 1 if syntax_error
@@ -1078,7 +1096,8 @@ EOB
1078
1096
  config_path = options.config_path or raise
1079
1097
  lock_path = Collection::Config.to_lockfile_path(config_path)
1080
1098
 
1081
- case args[0]
1099
+ subcommand = Abbrev.abbrev(['install', 'update', 'help'])[args[0]] || args[0]
1100
+ case subcommand
1082
1101
  when 'install'
1083
1102
  unless params[:frozen]
1084
1103
  Collection::Config.generate_lockfile(config_path: config_path, definition: Bundler.definition)
@@ -1154,5 +1173,80 @@ EOB
1154
1173
  opts.on('--frozen') if args[0] == 'install'
1155
1174
  end
1156
1175
  end
1176
+
1177
+ def run_subtract(args, _)
1178
+ write_to_file = false
1179
+ # @type var subtrahend_paths: Array[String]
1180
+ subtrahend_paths = []
1181
+
1182
+ opts = OptionParser.new do |opts|
1183
+ opts.banner = <<~HELP
1184
+ Usage:
1185
+ rbs subtract [options...] minuend.rbs [minuend2.rbs, ...] subtrahend.rbs
1186
+ rbs subtract [options...] minuend.rbs [minuend2.rbs, ...] --subtrahend subtrahend_1.rbs --subtrahend subtrahend_2.rbs
1187
+
1188
+ Remove duplications between RBS files.
1189
+
1190
+ Examples:
1191
+
1192
+ # Generate RBS files from the codebase.
1193
+ $ rbs prototype rb lib/ > generated.rbs
1194
+
1195
+ # Write more descrictive types by hand.
1196
+ $ $EDITOR handwritten.rbs
1197
+
1198
+ # Remove hand-written method definitions from generated.rbs.
1199
+ $ rbs subtract --write generated.rbs handwritten.rbs
1200
+
1201
+ Options:
1202
+ HELP
1203
+ opts.on('-w', '--write', 'Overwrite files directry') { write_to_file = true }
1204
+ opts.on('--subtrahend=PATH', '') { |path| subtrahend_paths << path }
1205
+ opts.parse!(args)
1206
+ end
1207
+
1208
+ if subtrahend_paths.empty?
1209
+ *minuend_paths, subtrahend_path = args
1210
+ unless subtrahend_path
1211
+ stdout.puts opts.help
1212
+ exit 1
1213
+ end
1214
+ subtrahend_paths << subtrahend_path
1215
+ else
1216
+ minuend_paths = args
1217
+ end
1218
+
1219
+ if minuend_paths.empty?
1220
+ stdout.puts opts.help
1221
+ exit 1
1222
+ end
1223
+
1224
+ subtrahend = Environment.new.tap do |env|
1225
+ loader = EnvironmentLoader.new(core_root: nil)
1226
+ subtrahend_paths.each do |path|
1227
+ loader.add(path: Pathname(path))
1228
+ end
1229
+ loader.load(env: env)
1230
+ end
1231
+
1232
+ minuend_paths.each do |minuend_path|
1233
+ FileFinder.each_file(Pathname(minuend_path), immediate: true, skip_hidden: true) do |rbs_path|
1234
+ buf = Buffer.new(name: rbs_path, content: rbs_path.read)
1235
+ _, dirs, decls = Parser.parse_signature(buf)
1236
+ subtracted = Subtractor.new(decls, subtrahend).call
1237
+
1238
+ io = StringIO.new
1239
+ w = Writer.new(out: io)
1240
+ w.write(dirs)
1241
+ w.write(subtracted)
1242
+
1243
+ if write_to_file
1244
+ rbs_path.write(io.string)
1245
+ else
1246
+ stdout.puts(io.string)
1247
+ end
1248
+ end
1249
+ end
1250
+ end
1157
1251
  end
1158
1252
  end
@@ -76,7 +76,7 @@ module RBS
76
76
  end
77
77
 
78
78
  if spec = gem_hash[dep.name]
79
- assign_gem(name: dep.name, version: spec.version, ignored_gems: ignored_gems, src_data: nil)
79
+ assign_gem(name: dep.name, version: spec.version, ignored_gems: ignored_gems, src_data: nil, skip: dep.source.is_a?(Bundler::Source::Gemspec))
80
80
  end
81
81
  end
82
82
 
@@ -91,50 +91,56 @@ module RBS
91
91
  end
92
92
  end
93
93
 
94
- private def assign_gem(name:, version:, src_data:, ignored_gems:)
94
+ private def assign_gem(name:, version:, src_data:, ignored_gems:, skip: false)
95
95
  return if ignored_gems.include?(name)
96
96
  return if lockfile.gems.key?(name)
97
97
 
98
- # @type var locked: Lockfile::library?
98
+ unless skip
99
+ # @type var locked: Lockfile::library?
99
100
 
100
- if existing_lockfile
101
- locked = existing_lockfile.gems[name]
102
- end
101
+ if existing_lockfile
102
+ locked = existing_lockfile.gems[name]
103
+ end
103
104
 
104
- # If rbs_collection.lock.yaml contain the gem, use it.
105
- # Else find the gem from gem_collection.
106
- unless locked
107
- source =
108
- if src_data
109
- Sources.from_config_entry(src_data, base_directory: config.config_path.dirname)
110
- else
111
- find_source(name: name)
105
+ # If rbs_collection.lock.yaml contain the gem, use it.
106
+ # Else find the gem from gem_collection.
107
+ unless locked
108
+ source =
109
+ if src_data
110
+ Sources.from_config_entry(src_data, base_directory: config.config_path.dirname)
111
+ else
112
+ find_source(name: name)
113
+ end
114
+
115
+ if source
116
+ installed_version = version
117
+ best_version = find_best_version(version: installed_version, versions: source.versions(name))
118
+
119
+ locked = {
120
+ name: name,
121
+ version: best_version.to_s,
122
+ source: source,
123
+ }
112
124
  end
113
-
114
- if source
115
- installed_version = version
116
- best_version = find_best_version(version: installed_version, versions: source.versions(name))
117
-
118
- locked = {
119
- name: name,
120
- version: best_version.to_s,
121
- source: source,
122
- }
123
125
  end
124
- end
125
126
 
126
- if locked
127
- lockfile.gems[name] = locked
127
+ if locked
128
+ lockfile.gems[name] = locked
128
129
 
129
- locked[:source].dependencies_of(locked[:name], locked[:version])&.each do |dep|
130
- assign_stdlib(name: dep["name"], from_gem: name)
130
+ locked[:source].dependencies_of(locked[:name], locked[:version])&.each do |dep|
131
+ assign_stdlib(name: dep["name"], from_gem: name)
132
+ end
131
133
  end
132
134
  end
133
135
 
134
- gem_hash[name].dependencies.each do |dep|
135
- if spec = gem_hash[dep.name]
136
- assign_gem(name: dep.name, version: spec.version, src_data: nil, ignored_gems: ignored_gems)
136
+ if spec = gem_hash.fetch(name, nil)
137
+ spec.dependencies.each do |dep|
138
+ if dep_spec = gem_hash[dep.name]
139
+ assign_gem(name: dep.name, version: dep_spec.version, src_data: nil, ignored_gems: ignored_gems)
140
+ end
137
141
  end
142
+ else
143
+ RBS.logger.warn "Cannot find `#{name}` gem. Using incorrect Bundler context? (#{definition.lockfile})"
138
144
  end
139
145
  end
140
146
 
@@ -239,14 +239,6 @@ module RBS
239
239
  definition.class_variables.merge!(defn.class_variables)
240
240
  end
241
241
 
242
- all_interfaces = one_ancestors.each_extended_interface.flat_map do |interface|
243
- other_interfaces = ancestor_builder.interface_ancestors(interface.name).ancestors #: Array[Definition::Ancestor::Instance]
244
- other_interfaces = other_interfaces.select {|ancestor| ancestor.source }
245
- [interface, *other_interfaces]
246
- end
247
- interface_methods = interface_methods(all_interfaces)
248
- import_methods(definition, type_name, methods, interface_methods, Substitution.new)
249
-
250
242
  one_ancestors.each_extended_module do |mod|
251
243
  mod.args.each do |arg|
252
244
  validate_type_presence(arg)
@@ -256,7 +248,12 @@ module RBS
256
248
  define_instance(definition, mod.name, subst)
257
249
  end
258
250
 
259
- interface_methods = interface_methods(one_ancestors.each_extended_interface.to_a)
251
+ all_interfaces = one_ancestors.each_extended_interface.flat_map do |interface|
252
+ other_interfaces = ancestor_builder.interface_ancestors(interface.name).ancestors #: Array[Definition::Ancestor::Instance]
253
+ other_interfaces = other_interfaces.select {|ancestor| ancestor.source }
254
+ [interface, *other_interfaces]
255
+ end
256
+ interface_methods = interface_methods(all_interfaces)
260
257
  import_methods(definition, type_name, methods, interface_methods, Substitution.new)
261
258
 
262
259
  entry.decls.each do |d|
@@ -365,7 +365,7 @@ module RBS
365
365
  name = decl.name.with_prefix(namespace)
366
366
 
367
367
  if interface_entry = interface_decls[name]
368
- DuplicatedDeclarationError.new(name, decl, interface_entry.decl)
368
+ raise DuplicatedDeclarationError.new(name, decl, interface_entry.decl)
369
369
  end
370
370
 
371
371
  interface_decls[name] = InterfaceEntry.new(name: name, decl: decl, outer: outer)
@@ -374,7 +374,7 @@ module RBS
374
374
  name = decl.name.with_prefix(namespace)
375
375
 
376
376
  if entry = type_alias_decls[name]
377
- DuplicatedDeclarationError.new(name, decl, entry.decl)
377
+ raise DuplicatedDeclarationError.new(name, decl, entry.decl)
378
378
  end
379
379
 
380
380
  type_alias_decls[name] = TypeAliasEntry.new(name: name, decl: decl, outer: outer)
@@ -12,6 +12,8 @@ module RBS
12
12
  end
13
13
  end
14
14
 
15
+ include FileFinder
16
+
15
17
  Library = _ = Struct.new(:name, :version, keyword_init: true)
16
18
 
17
19
  attr_reader :core_root
@@ -129,35 +131,13 @@ module RBS
129
131
  end
130
132
  end
131
133
 
132
- def each_file(path, immediate:, skip_hidden:, &block)
133
- case
134
- when path.file?
135
- if path.extname == ".rbs" || immediate
136
- yield path
137
- end
138
-
139
- when path.directory?
140
- if path.basename.to_s.start_with?("_")
141
- if skip_hidden
142
- unless immediate
143
- return
144
- end
145
- end
146
- end
147
-
148
- path.children.sort.each do |child|
149
- each_file(child, immediate: false, skip_hidden: skip_hidden, &block)
150
- end
151
- end
152
- end
153
-
154
134
  def each_signature
155
135
  files = Set[]
156
136
 
157
137
  each_dir do |source, dir|
158
138
  skip_hidden = !source.is_a?(Pathname)
159
139
 
160
- each_file(dir, skip_hidden: skip_hidden, immediate: true) do |path|
140
+ FileFinder.each_file(dir, skip_hidden: skip_hidden, immediate: true) do |path|
161
141
  next if files.include?(path)
162
142
 
163
143
  files << path
data/lib/rbs/errors.rb CHANGED
@@ -22,11 +22,16 @@ module RBS
22
22
 
23
23
  module DetailedMessageable
24
24
  def detailed_message(highlight: false, **)
25
+ msg = super
26
+
27
+ # Support only one line
28
+ return msg unless location.start_line == location.end_line
29
+
25
30
  indent = " " * location.start_column
26
31
  marker = "^" * (location.end_column - location.start_column)
27
32
 
28
33
  io = StringIO.new
29
- io.puts super
34
+ io.puts msg
30
35
  io.puts
31
36
  io.print "\e[1m" if highlight
32
37
  io.puts " #{location.buffer.lines[location.end_line - 1]}"
@@ -125,7 +130,9 @@ module RBS
125
130
  end
126
131
  end
127
132
 
128
- class NoTypeFoundError < BaseError
133
+ class NoTypeFoundError < DefinitionError
134
+ include DetailedMessageable
135
+
129
136
  attr_reader :type_name
130
137
  attr_reader :location
131
138
 
@@ -163,6 +170,8 @@ module RBS
163
170
  end
164
171
 
165
172
  class InheritModuleError < DefinitionError
173
+ include DetailedMessageable
174
+
166
175
  attr_reader :super_decl
167
176
 
168
177
  def initialize(super_decl)
@@ -171,6 +180,10 @@ module RBS
171
180
  super "#{Location.to_string(super_decl.location)}: Cannot inherit a module: #{super_decl.name}"
172
181
  end
173
182
 
183
+ def location
184
+ @super_decl.location
185
+ end
186
+
174
187
  def self.check!(super_decl, env:)
175
188
  return if env.class_decl?(super_decl.name) || env.class_alias?(super_decl.name)
176
189
 
@@ -179,6 +192,8 @@ module RBS
179
192
  end
180
193
 
181
194
  class NoSelfTypeFoundError < DefinitionError
195
+ include DetailedMessageable
196
+
182
197
  attr_reader :type_name
183
198
  attr_reader :location
184
199
 
@@ -197,6 +212,8 @@ module RBS
197
212
  end
198
213
 
199
214
  class NoMixinFoundError < DefinitionError
215
+ include DetailedMessageable
216
+
200
217
  attr_reader :type_name
201
218
  attr_reader :member
202
219
 
@@ -217,6 +234,8 @@ module RBS
217
234
  end
218
235
 
219
236
  class DuplicatedMethodDefinitionError < DefinitionError
237
+ include DetailedMessageable
238
+
220
239
  attr_reader :type
221
240
  attr_reader :method_name
222
241
  attr_reader :members
@@ -256,6 +275,8 @@ module RBS
256
275
  end
257
276
 
258
277
  class DuplicatedInterfaceMethodDefinitionError < DefinitionError
278
+ include DetailedMessageable
279
+
259
280
  attr_reader :type
260
281
  attr_reader :method_name
261
282
  attr_reader :member
@@ -268,6 +289,10 @@ module RBS
268
289
  super "#{member.location}: Duplicated method definition: #{qualified_method_name}"
269
290
  end
270
291
 
292
+ def location
293
+ member.location
294
+ end
295
+
271
296
  def qualified_method_name
272
297
  case type
273
298
  when Types::ClassSingleton
@@ -283,6 +308,8 @@ module RBS
283
308
  end
284
309
 
285
310
  class UnknownMethodAliasError < DefinitionError
311
+ include DetailedMessageable
312
+
286
313
  attr_reader :type_name
287
314
  attr_reader :original_name
288
315
  attr_reader :aliased_name
@@ -310,6 +337,8 @@ module RBS
310
337
  end
311
338
 
312
339
  class InvalidOverloadMethodError < DefinitionError
340
+ include DetailedMessageable
341
+
313
342
  attr_reader :type_name
314
343
  attr_reader :method_name
315
344
  attr_reader :kind
@@ -330,6 +359,10 @@ module RBS
330
359
 
331
360
  super "#{Location.to_string members[0].location}: Invalid method overloading: #{type_name}#{delimiter}#{method_name}"
332
361
  end
362
+
363
+ def location
364
+ members[0].location
365
+ end
333
366
  end
334
367
 
335
368
  class GenericParameterMismatchError < LoadingError
@@ -357,6 +390,8 @@ module RBS
357
390
  end
358
391
 
359
392
  class InvalidVarianceAnnotationError < DefinitionError
393
+ include DetailedMessageable
394
+
360
395
  attr_reader :type_name
361
396
  attr_reader :param
362
397
  attr_reader :location
@@ -371,6 +406,8 @@ module RBS
371
406
  end
372
407
 
373
408
  class RecursiveAliasDefinitionError < DefinitionError
409
+ include DetailedMessageable
410
+
374
411
  attr_reader :type
375
412
  attr_reader :defs
376
413
 
@@ -389,6 +426,8 @@ module RBS
389
426
  end
390
427
 
391
428
  class MixinClassError < DefinitionError
429
+ include DetailedMessageable
430
+
392
431
  attr_reader :type_name
393
432
  attr_reader :member
394
433
 
@@ -426,6 +465,8 @@ module RBS
426
465
  end
427
466
 
428
467
  class RecursiveTypeAliasError < BaseError
468
+ include DetailedMessageable
469
+
429
470
  attr_reader :alias_names
430
471
  attr_reader :location
431
472
 
@@ -442,6 +483,8 @@ module RBS
442
483
  end
443
484
 
444
485
  class NonregularTypeAliasError < BaseError
486
+ include DetailedMessageable
487
+
445
488
  attr_reader :diagnostic
446
489
  attr_reader :location
447
490
 
@@ -454,6 +497,8 @@ module RBS
454
497
  end
455
498
 
456
499
  class CyclicTypeParameterBound < BaseError
500
+ include DetailedMessageable
501
+
457
502
  attr_reader :params, :type_name, :method_name, :location
458
503
 
459
504
  def initialize(type_name:, method_name:, params:, location:)
@@ -467,6 +512,8 @@ module RBS
467
512
  end
468
513
 
469
514
  class InconsistentClassModuleAliasError < BaseError
515
+ include DetailedMessageable
516
+
470
517
  attr_reader :alias_entry
471
518
 
472
519
  def initialize(entry)
@@ -482,9 +529,15 @@ module RBS
482
529
 
483
530
  super "#{Location.to_string(entry.decl.location&.[](:old_name))}: A #{expected_kind} `#{entry.decl.new_name}` cannot be an alias of a #{actual_kind} `#{entry.decl.old_name}`"
484
531
  end
532
+
533
+ def location
534
+ @alias_entry.decl.location
535
+ end
485
536
  end
486
537
 
487
538
  class CyclicClassAliasDefinitionError < BaseError
539
+ include DetailedMessageable
540
+
488
541
  attr_reader :alias_entry
489
542
 
490
543
  def initialize(entry)
@@ -492,5 +545,9 @@ module RBS
492
545
 
493
546
  super "#{Location.to_string(entry.decl.location&.[](:old_name))}: A #{alias_entry.decl.new_name} is a cyclic definition"
494
547
  end
548
+
549
+ def location
550
+ @alias_entry.decl.location
551
+ end
495
552
  end
496
553
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBS
4
+ module FileFinder
5
+ module_function
6
+
7
+ def self.each_file(path, immediate:, skip_hidden:, &block)
8
+ return enum_for(__method__, path, immediate: immediate, skip_hidden: skip_hidden) unless block
9
+
10
+ case
11
+ when path.file?
12
+ if path.extname == ".rbs" || immediate
13
+ yield path
14
+ end
15
+
16
+ when path.directory?
17
+ if path.basename.to_s.start_with?("_")
18
+ if skip_hidden
19
+ unless immediate
20
+ return
21
+ end
22
+ end
23
+ end
24
+
25
+ path.children.sort.each do |child|
26
+ each_file(child, immediate: false, skip_hidden: skip_hidden, &block)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -2,14 +2,14 @@
2
2
 
3
3
  module RBS
4
4
  class Parser
5
- def self.parse_type(source, range: 0..., variables: [])
5
+ def self.parse_type(source, range: 0..., variables: [], require_eof: false)
6
6
  buf = buffer(source)
7
- _parse_type(buf, range.begin || 0, range.end || buf.last_position, variables)
7
+ _parse_type(buf, range.begin || 0, range.end || buf.last_position, variables, require_eof)
8
8
  end
9
9
 
10
- def self.parse_method_type(source, range: 0..., variables: [])
10
+ def self.parse_method_type(source, range: 0..., variables: [], require_eof: false)
11
11
  buf = buffer(source)
12
- _parse_method_type(buf, range.begin || 0, range.end || buf.last_position, variables)
12
+ _parse_method_type(buf, range.begin || 0, range.end || buf.last_position, variables, require_eof)
13
13
  end
14
14
 
15
15
  def self.parse_signature(source)
@@ -215,7 +215,8 @@ module RBS
215
215
  when :include
216
216
  args.each do |arg|
217
217
  if (name = const_to_name(arg, context: context))
218
- decls << AST::Members::Include.new(
218
+ klass = context.singleton ? AST::Members::Extend : AST::Members::Include
219
+ decls << klass.new(
219
220
  name: name,
220
221
  args: [],
221
222
  annotations: [],