sord 3.0.1 → 4.0.0

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: c04d27e18791a912df49bf20c04c3d8555c04ce7f4e9d97d566398c705b4d826
4
- data.tar.gz: c9868a430d1cace15d5c11463c7718aa768e2f4b195c9877ba0cb9a995bb8d7e
3
+ metadata.gz: dc4c58d9ccea9406b38e20c3d6d92357a962e49aa91884b421a496d9302566d2
4
+ data.tar.gz: cdcc1158ce99895796423ba6b833f68e261e09d6a61b6892104d5948a3c64625
5
5
  SHA512:
6
- metadata.gz: 2e1266f3b0950874993fb09814431d6ccc7e379128b7146bd46865571d422676df3103ec602534d2cd2e517e1b24b098d8e1edb09cb7002226369fa41d65c127
7
- data.tar.gz: c358bd3954399bc55db8883720a40bf1452131771367eb0613637899f5c4d319af6ee3c4d261cb70cb355d83cc21339489562ec1847c47f2893cb40a9c192818
6
+ metadata.gz: 984cf951dc9a0ee9ea6180402e60817c866d171f313c102a34f4d8d20cc36ca95d11c24082b46810b70eb35fa6e67643bd4b9e3007889ec7a87f25fa0db3c44d
7
+ data.tar.gz: b9f25ab1affc00c9c2a25b90db7735c1ee8c89e53cd98ef1a04087b58d9efe2f8878e54f6a1cbdc913c38142507aaff74a9e437151cd74fd5f7b612cbab4f38f
@@ -0,0 +1,22 @@
1
+ name: Run tests
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ strategy:
8
+ matrix:
9
+ ruby: [2.4, 2.5, 2.6, 2.7, 3.0]
10
+ continue-on-error: false
11
+
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v2
15
+ - name: Set up Ruby
16
+ uses: ruby/setup-ruby@v1
17
+ with:
18
+ ruby-version: ${{ matrix.ruby }}
19
+ - name: Install dependencies
20
+ run: bundle install
21
+ - name: Run tests
22
+ run: bundle exec rake
data/CHANGELOG.md CHANGED
@@ -3,6 +3,24 @@ All notable changes to this project will be documented in this file.
3
3
 
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5
5
 
6
+ ## [4.0.0] - 2022-07-19
7
+ ### Added
8
+ - Constants are now assigned types when generating RBS, using `@return`.
9
+ - Class-level `attr_accessor`s are converted to methods when generating RBS.
10
+ - Added the `--exclude-untyped` flag, which skips generating type signatures for methods with
11
+ `untyped` return values.
12
+
13
+ ### Changed
14
+ - If YARD tags are present for a block, but there is no block param (such as when using `yield`),
15
+ the type signature now includes the documented block. This could be a **breaking change** leading
16
+ to type errors in existing code where such methods are called.
17
+
18
+ ### Fixed
19
+ - Added workaround for YARD syntax error when a default parameter value begins with a unary minus
20
+ - Name resolutions from the root (e.g. `::X`) now behave correctly; previously they may have
21
+ selected a class named `X` nested within another namespace. This may be a **breaking change** if
22
+ any part of your generated type signatures was relying on the old, buggy behaviour.
23
+
6
24
  ## [3.0.1] - 2020-12-28
7
25
  ### Fixed
8
26
  - Fixed `SortedSet` crash on Ruby 3
data/README.md CHANGED
@@ -12,6 +12,8 @@ type signatures you need!
12
12
  Sord is the perfect way to jump-start the adoption of types in your project,
13
13
  whether you plan to use Sorbet's RBI format or Ruby 3/Steep's RBS format.
14
14
 
15
+ **Try Sord online at: [sord.aaronc.cc](https://sord.aaronc.cc)**
16
+
15
17
  Sord has the following features:
16
18
  - Automatically generates signatures for modules, classes and methods
17
19
  - Support for multiple parameter or return types (`T.any`/`|`)
@@ -81,6 +83,8 @@ Sord also takes some flags to alter the generated file:
81
83
  take a comma-separated list of logging kinds, for example `omit,infer`.
82
84
  When using `--include-messages`, the `done` kind is included by default.
83
85
  (You cannot specify both `--include-messages` and `--exclude-messages`.)
86
+ - `--exclude-untyped`: Exclude methods and attributes with untyped return
87
+ values.
84
88
 
85
89
  ## Example
86
90
 
data/exe/sord CHANGED
@@ -23,6 +23,7 @@ command :gen do |c|
23
23
  c.option '--keep-original-comments', 'Retains original YARD comments rather than converting them to Markdown'
24
24
  c.option '--skip-constants', 'Excludes constants from generated file'
25
25
  c.option '--use-original-initialize-return', 'Uses the specified return type for #initialize rather than void'
26
+ c.option '--exclude-untyped', 'Exclude methods and attributes with untyped return values'
26
27
 
27
28
  c.action do |args, options|
28
29
  options.default(
@@ -38,16 +39,17 @@ command :gen do |c|
38
39
  keep_original_comments: false,
39
40
  skip_constants: false,
40
41
  use_original_initialize_return: false,
42
+ exclude_untyped: false,
41
43
  )
42
44
 
43
45
  if args.length != 1
44
46
  Sord::Logging.error('Must specify filename')
45
47
  exit 1
46
48
  end
47
-
49
+
48
50
  plugin_options = options.__hash__
49
- plugin_options[:exclude_options] = plugin_options[:exclude_options]&.split(',')
50
- plugin_options[:include_options] = plugin_options[:include_options]&.split(',')
51
+ plugin_options[:exclude_messages] = plugin_options[:exclude_messages]&.split(',')
52
+ plugin_options[:include_messages] = plugin_options[:include_messages]&.split(',')
51
53
 
52
54
  if !(plugin_options[:rbi] || plugin_options[:rbs])
53
55
  if args.first
@@ -8,16 +8,16 @@ require 'rainbow'
8
8
  module Sord
9
9
  # Converts the current working directory's YARD registry into an type
10
10
  # signature file.
11
- class Generator
11
+ class Generator
12
12
  VALID_MODES = [:rbi, :rbs]
13
13
 
14
- # @return [Integer] The number of objects this generator has processed so
14
+ # @return [Integer] The number of objects this generator has processed so
15
15
  # far.
16
16
  def object_count
17
17
  @namespace_count + @method_count
18
18
  end
19
19
 
20
- # @return [Array<Array(String, YARD::CodeObjects::Base, Integer)>] The
20
+ # @return [Array<Array(String, YARD::CodeObjects::Base, Integer)>] The
21
21
  # errors encountered by by the generator. Each element is of the form
22
22
  # [message, item, line].
23
23
  attr_reader :warnings
@@ -55,10 +55,19 @@ module Sord
55
55
  @keep_original_comments = options[:keep_original_comments]
56
56
  @skip_constants = options[:skip_constants]
57
57
  @use_original_initialize_return = options[:use_original_initialize_return]
58
+ @exclude_untyped = options[:exclude_untyped]
58
59
 
59
60
  # Hook the logger so that messages are added as comments
60
- Logging.add_hook do |type, msg, item|
61
- @current_object.add_comment_to_next_child("sord #{type} - #{msg}")
61
+ Logging.add_hook do |type, msg, item, **opts|
62
+ # Hack: the "exclude untyped" log message needs to print somewhere, but
63
+ # if there's no future object for that comment to associate with, it'll
64
+ # never be printed!
65
+ # Instead, add an arbitrary containing the comment
66
+ if opts[:immediate]
67
+ @current_object.create_arbitrary(code: "# sord #{type} - #{msg}")
68
+ else
69
+ @current_object.add_comment_to_next_child("sord #{type} - #{msg}")
70
+ end
62
71
  end if options[:sord_comments]
63
72
 
64
73
  # Hook the logger so that warnings are collected
@@ -110,7 +119,7 @@ module Sord
110
119
  def add_constants(item)
111
120
  inserted_constant_names = Set.new
112
121
 
113
- item.constants(included: false).each do |constant|
122
+ item.constants(included: false).each do |constant|
114
123
  # Take a constant (like "A::B::CONSTANT"), split it on each '::', and
115
124
  # set the constant name to the last string in the array.
116
125
  constant_name = constant.to_s.split('::').last
@@ -119,7 +128,7 @@ module Sord
119
128
  next
120
129
  end
121
130
  inserted_constant_names << constant_name
122
-
131
+
123
132
  # Add the constant to the current object being generated.
124
133
  case @mode
125
134
  when :rbi
@@ -127,7 +136,19 @@ module Sord
127
136
  c.add_comments(constant.docstring.all.split("\n"))
128
137
  end
129
138
  when :rbs
130
- @current_object.create_constant(constant_name, type: Parlour::Types::Untyped.new) do |c|
139
+ return_tags = constant.tags('return')
140
+ returns = if return_tags.empty?
141
+ Logging.omit("no YARD return type given, using untyped", constant)
142
+ Parlour::Types::Untyped.new
143
+ else
144
+ TypeConverter.yard_to_parlour(
145
+ return_tags.map(&:types).flatten,
146
+ constant,
147
+ @replace_errors_with_untyped,
148
+ @replace_unresolved_with_untyped
149
+ )
150
+ end
151
+ @current_object.create_constant(constant_name, type: returns) do |c|
131
152
  c.add_comments(constant.docstring.all.split("\n"))
132
153
  end
133
154
  end
@@ -243,17 +264,23 @@ module Sord
243
264
  if meth.is_attribute?
244
265
  next
245
266
  end
246
-
267
+
247
268
  # Sort parameters
248
269
  meth.parameters.reverse.sort! { |pair1, pair2| sort_params(pair1, pair2) }
249
- # This is better than iterating over YARD's "@param" tags directly
270
+ # This is better than iterating over YARD's "@param" tags directly
250
271
  # because it includes parameters without documentation
251
272
  # (The gsubs allow for better splat-argument compatibility)
252
273
  parameter_names_and_defaults_to_tags = meth.parameters.map do |name, default|
253
- [[name, default], meth.tags('param')
274
+ [[name, fix_default_if_unary_minus(default)], meth.tags('param')
254
275
  .find { |p| p.name&.gsub('*', '')&.gsub(':', '') == name.gsub('*', '').gsub(':', '') }]
255
276
  end.to_h
256
277
 
278
+ # Add block param if there is no named param but YARD tags are present
279
+ if !parameter_names_and_defaults_to_tags.any? { |((name, _), _)| name.start_with? '&' } \
280
+ && (meth.tags('yieldparam').any? || meth.tag('yieldreturn'))
281
+ parameter_names_and_defaults_to_tags[['&blk']] = nil
282
+ end
283
+
257
284
  parameter_types = parameter_names_and_defaults_to_tags.map do |name_and_default, tag|
258
285
  name = name_and_default.first
259
286
 
@@ -290,10 +317,10 @@ module Sord
290
317
  if parameter_names_and_defaults_to_tags.length == 1 \
291
318
  && meth.tags('param').length == 1 \
292
319
  && meth.tag('param').types
293
-
320
+
294
321
  Logging.infer("argument name in single @param inferred as #{parameter_names_and_defaults_to_tags.first.first.first.inspect}", meth)
295
322
  next TypeConverter.yard_to_parlour(meth.tag('param').types, meth, @replace_errors_with_untyped, @replace_unresolved_with_untyped)
296
- else
323
+ else
297
324
  Logging.omit("no YARD type given for #{name.inspect}, using untyped", meth)
298
325
  next Parlour::Types::Untyped.new
299
326
  end
@@ -306,7 +333,7 @@ module Sord
306
333
  end
307
334
  inferred_type = TypeConverter.yard_to_parlour(
308
335
  return_types, meth, @replace_errors_with_untyped, @replace_unresolved_with_untyped)
309
-
336
+
310
337
  Logging.infer("inferred type of parameter #{name.inspect} as #{inferred_type.describe} using getter's return type", meth)
311
338
  inferred_type
312
339
  else
@@ -341,7 +368,7 @@ module Sord
341
368
  parlour_params = parameter_names_and_defaults_to_tags
342
369
  .zip(parameter_types)
343
370
  .map do |((name, default), _), type|
344
- # If the default is "nil" but the type is not nilable, then it
371
+ # If the default is "nil" but the type is not nilable, then it
345
372
  # should become nilable
346
373
  # (T.untyped can include nil, so don't alter that)
347
374
  type = Parlour::Types::Nilable.new(type) \
@@ -369,10 +396,15 @@ module Sord
369
396
  end
370
397
  .compact
371
398
 
399
+ if @exclude_untyped && parlour_params.all? { |p| p.type.is_a?(Parlour::Types::Untyped) } && returns.is_a?(Parlour::Types::Untyped)
400
+ Logging.omit("excluding untyped", meth, immediate: true)
401
+ next
402
+ end
403
+
372
404
  case @mode
373
405
  when :rbi
374
406
  @current_object.create_method(
375
- meth.name.to_s,
407
+ meth.name.to_s,
376
408
  parameters: parlour_params,
377
409
  returns: returns,
378
410
  class_method: meth.scope == :class
@@ -391,7 +423,7 @@ module Sord
391
423
  ) do |m|
392
424
  add_comments(meth, m)
393
425
  end
394
- end
426
+ end
395
427
  end
396
428
  end
397
429
 
@@ -445,6 +477,11 @@ module Sord
445
477
  kind = :writer
446
478
  end
447
479
 
480
+ if @exclude_untyped && parlour_type.is_a?(Parlour::Types::Untyped)
481
+ Logging.omit("excluding untyped attribute", reader || writer, immediate: true)
482
+ next
483
+ end
484
+
448
485
  case @mode
449
486
  when :rbi
450
487
  @current_object.create_attribute(
@@ -457,7 +494,31 @@ module Sord
457
494
  end
458
495
  when :rbs
459
496
  if attr_loc == :class
460
- Logging.warn("RBS doesn't support class attributes, dropping", reader || writer)
497
+ # RBS doesn't support class attr_accessors so create individual methods
498
+
499
+ if reader
500
+ @current_object.create_method(
501
+ name.to_s,
502
+ [Parlour::RbsGenerator::MethodSignature.new([], parlour_type)],
503
+ class_method: true
504
+ ) do |m|
505
+ add_comments(reader, m)
506
+ end
507
+ end
508
+
509
+ if writer
510
+ @current_object.create_method(
511
+ "#{name}=",
512
+ [Parlour::RbsGenerator::MethodSignature.new([Parlour::RbsGenerator::Parameter.new(
513
+ "value",
514
+ type: parlour_type,
515
+ required: true
516
+ )], parlour_type)],
517
+ class_method: true
518
+ ) do |m|
519
+ add_comments(writer, m)
520
+ end
521
+ end
461
522
  else
462
523
  @current_object.create_attribute(
463
524
  name.to_s,
@@ -499,7 +560,7 @@ module Sord
499
560
  @current_object = parent
500
561
  end
501
562
 
502
- # Populates the generator with the contents of the YARD registry. You
563
+ # Populates the generator with the contents of the YARD registry. You
503
564
  # must load the YARD registry first!
504
565
  # @return [void]
505
566
  def populate
@@ -597,5 +658,20 @@ module Sord
597
658
 
598
659
  return pair_type_order[pair1_type] <=> pair_type_order[pair2_type]
599
660
  end
661
+
662
+ # Removes the last character of a default parameter value if it begins with
663
+ # '-', working around a bug in YARD. (See lsegal/yard #894)
664
+ #
665
+ # @param [String] default
666
+ # @return [String, nil]
667
+ def fix_default_if_unary_minus(default)
668
+ if default.nil?
669
+ nil
670
+ elsif default[0] == '-' && !default.start_with?('->')
671
+ default[0..-2]
672
+ else
673
+ default
674
+ end
675
+ end
600
676
  end
601
677
  end
data/lib/sord/logging.rb CHANGED
@@ -70,16 +70,17 @@ module Sord
70
70
  # is associated with, if any. This is shown before the log message if it is
71
71
  # specified.
72
72
  # @return [void]
73
- def self.generic(kind, header, msg, item)
73
+ def self.generic(kind, header, msg, item, **opts)
74
74
  return unless enabled_types.include?(kind)
75
75
 
76
- if item
77
- puts "#{header} (#{Rainbow(item.path).bold}) #{msg}" unless silent?
76
+ message = if item
77
+ "#{header} (#{Rainbow(item.path).bold}) #{msg}"
78
78
  else
79
- puts "#{header} #{msg}" unless silent?
79
+ "#{header} #{msg}"
80
80
  end
81
+ puts message unless silent?
81
82
 
82
- invoke_hooks(kind, msg, item)
83
+ invoke_hooks(kind, msg, item, **opts)
83
84
  end
84
85
 
85
86
  # Print a warning message. This should be used for things which require the
@@ -89,8 +90,8 @@ module Sord
89
90
  # is associated with, if any. This is shown before the log message if it is
90
91
  # specified.
91
92
  # @return [void]
92
- def self.warn(msg, item = nil)
93
- generic(:warn, Rainbow('[WARN ]').yellow, msg, item)
93
+ def self.warn(msg, item = nil, **opts)
94
+ generic(:warn, Rainbow('[WARN ]').yellow, msg, item, **opts)
94
95
  end
95
96
 
96
97
  # Print an info message. This should be used for generic informational
@@ -100,8 +101,8 @@ module Sord
100
101
  # is associated with, if any. This is shown before the log message if it is
101
102
  # specified.
102
103
  # @return [void]
103
- def self.info(msg, item = nil)
104
- generic(:info, '[INFO ]', msg, item)
104
+ def self.info(msg, item = nil, **opts)
105
+ generic(:info, '[INFO ]', msg, item, **opts)
105
106
  end
106
107
 
107
108
  # Print a duck-typing message. This should be used when the YARD
@@ -112,8 +113,8 @@ module Sord
112
113
  # is associated with, if any. This is shown before the log message if it is
113
114
  # specified.
114
115
  # @return [void]
115
- def self.duck(msg, item = nil)
116
- generic(:duck, Rainbow('[DUCK ]').cyan, msg, item)
116
+ def self.duck(msg, item = nil, **opts)
117
+ generic(:duck, Rainbow('[DUCK ]').cyan, msg, item, **opts)
117
118
  end
118
119
 
119
120
  # Print an error message. This should be used for things which require the
@@ -123,8 +124,8 @@ module Sord
123
124
  # is associated with, if any. This is shown before the log message if it is
124
125
  # specified.
125
126
  # @return [void]
126
- def self.error(msg, item = nil)
127
- generic(:error, Rainbow('[ERROR]').red, msg, item)
127
+ def self.error(msg, item = nil, **opts)
128
+ generic(:error, Rainbow('[ERROR]').red, msg, item, **opts)
128
129
  end
129
130
 
130
131
  # Print an infer message. This should be used when the user should be told
@@ -135,8 +136,8 @@ module Sord
135
136
  # is associated with, if any. This is shown before the log message if it is
136
137
  # specified.
137
138
  # @return [void]
138
- def self.infer(msg, item = nil)
139
- generic(:infer, Rainbow('[INFER]').blue, msg, item)
139
+ def self.infer(msg, item = nil, **opts)
140
+ generic(:infer, Rainbow('[INFER]').blue, msg, item, **opts)
140
141
  end
141
142
 
142
143
  # Print an omit message. This should be used as a special type of warning
@@ -147,8 +148,8 @@ module Sord
147
148
  # is associated with, if any. This is shown before the log message if it is
148
149
  # specified.
149
150
  # @return [void]
150
- def self.omit(msg, item = nil)
151
- generic(:omit, Rainbow('[OMIT ]').magenta, msg, item)
151
+ def self.omit(msg, item = nil, **opts)
152
+ generic(:omit, Rainbow('[OMIT ]').magenta, msg, item, **opts)
152
153
  end
153
154
 
154
155
  # Print a done message. This should be used when a process completes
@@ -158,8 +159,8 @@ module Sord
158
159
  # is associated with, if any. This is shown before the log message if it is
159
160
  # specified.
160
161
  # @return [void]
161
- def self.done(msg, item = nil)
162
- generic(:done, Rainbow('[DONE ]').green, msg, item)
162
+ def self.done(msg, item = nil, **opts)
163
+ generic(:done, Rainbow('[DONE ]').green, msg, item, **opts)
163
164
  end
164
165
 
165
166
  # Invokes all registered hooks on the logger.
@@ -169,9 +170,9 @@ module Sord
169
170
  # is associated with, if any. This is shown before the log message if it is
170
171
  # specified.
171
172
  # @return [void]
172
- def self.invoke_hooks(kind, msg, item)
173
+ def self.invoke_hooks(kind, msg, item, **opts)
173
174
  @@hooks.each do |hook|
174
- hook.(kind, msg, item) rescue nil
175
+ hook.(kind, msg, item, **opts)
175
176
  end
176
177
  end
177
178
 
data/lib/sord/resolver.rb CHANGED
@@ -24,6 +24,10 @@ module Sord
24
24
  # @return [Array<String>]
25
25
  def self.paths_for(name)
26
26
  prepare
27
+
28
+ # If the name starts with ::, then we've been given an explicit path from root - just use that
29
+ return [name] if name.start_with?('::')
30
+
27
31
  (@@names_to_paths[name.split('::').last] || [])
28
32
  .select { |x| x.end_with?(name) }
29
33
  end
data/lib/sord/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # typed: strong
2
2
  module Sord
3
- VERSION = '3.0.1'
3
+ VERSION = '4.0.0'
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sord
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.1
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Christiansen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-28 00:00:00.000000000 Z
11
+ date: 2022-07-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: yard
@@ -146,6 +146,7 @@ extra_rdoc_files: []
146
146
  files:
147
147
  - ".github/ISSUE_TEMPLATE/bug_report.md"
148
148
  - ".github/ISSUE_TEMPLATE/feature-request.md"
149
+ - ".github/workflows/ruby.yml"
149
150
  - ".gitignore"
150
151
  - ".parlour"
151
152
  - ".rspec"
@@ -185,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
185
186
  - !ruby/object:Gem::Version
186
187
  version: '0'
187
188
  requirements: []
188
- rubygems_version: 3.0.3
189
+ rubygems_version: 3.2.22
189
190
  signing_key:
190
191
  specification_version: 4
191
192
  summary: Generate Sorbet RBI files from YARD documentation