rbs 3.0.4 → 3.1.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.
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: [],