docscribe 1.6.0 → 1.6.2
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 +4 -4
- data/README.md +76 -193
- data/exe/docscribe-client +26 -7
- data/lib/docscribe/cli/config_builder.rb +37 -2
- data/lib/docscribe/cli/coverage.rb +5 -5
- data/lib/docscribe/cli/formatters/json.rb +74 -29
- data/lib/docscribe/cli/formatters/sarif.rb +20 -3
- data/lib/docscribe/cli/options.rb +17 -2
- data/lib/docscribe/cli/rbs_gen.rb +4 -4
- data/lib/docscribe/cli/run.rb +107 -24
- data/lib/docscribe/cli/update_types.rb +61 -17
- data/lib/docscribe/cli.rb +19 -13
- data/lib/docscribe/config/defaults.rb +1 -0
- data/lib/docscribe/config/rbs.rb +22 -1
- data/lib/docscribe/config/template.rb +3 -0
- data/lib/docscribe/config/validation.rb +19 -0
- data/lib/docscribe/config.rb +1 -0
- data/lib/docscribe/infer/behavior.rb +13 -13
- data/lib/docscribe/infer/params.rb +2 -2
- data/lib/docscribe/infer/raises.rb +5 -6
- data/lib/docscribe/infer/returns.rb +1612 -151
- data/lib/docscribe/infer.rb +7 -7
- data/lib/docscribe/inline_rewriter/doc_builder.rb +485 -102
- data/lib/docscribe/inline_rewriter.rb +263 -97
- data/lib/docscribe/plugin/registry.rb +1 -0
- data/lib/docscribe/server/base.rb +255 -0
- data/lib/docscribe/server/client.rb +95 -0
- data/lib/docscribe/server/daemon.rb +678 -0
- data/lib/docscribe/server/protocol.rb +50 -0
- data/lib/docscribe/server.rb +4 -835
- data/lib/docscribe/types/primitive.rb +160 -0
- data/lib/docscribe/types/sorbet/base_provider.rb +33 -1
- data/lib/docscribe/types/yard/formatter.rb +35 -6
- data/lib/docscribe/types/yard/parser.rb +25 -20
- data/lib/docscribe/types/yard/validator.rb +131 -0
- data/lib/docscribe/validator/generic_compatibility.rb +698 -0
- data/lib/docscribe/validator/type_mismatch_validator.rb +287 -0
- data/lib/docscribe/version.rb +1 -1
- metadata +12 -3
|
@@ -10,7 +10,7 @@ module Docscribe
|
|
|
10
10
|
# Two-pass update: rebuild docs then re-merge with RBS types.
|
|
11
11
|
#
|
|
12
12
|
# Usage:
|
|
13
|
-
# docscribe update_types [directory]
|
|
13
|
+
# docscribe update_types [directory|file]
|
|
14
14
|
#
|
|
15
15
|
# Pass 1: `-AkB --rbs-collection <dir>` — aggressive rebuild, keep descriptions,
|
|
16
16
|
# no boilerplate, using RBS collection signatures.
|
|
@@ -18,7 +18,7 @@ module Docscribe
|
|
|
18
18
|
# using RBS collection signatures.
|
|
19
19
|
module UpdateTypes
|
|
20
20
|
BANNER = <<~TEXT
|
|
21
|
-
Usage: docscribe update_types [directory]
|
|
21
|
+
Usage: docscribe update_types [directory|file] [options]
|
|
22
22
|
|
|
23
23
|
Two-pass type-aware documentation update.
|
|
24
24
|
|
|
@@ -28,6 +28,8 @@ module Docscribe
|
|
|
28
28
|
Pass 2 (safe): docscribe -aB --rbs-collection <dir>
|
|
29
29
|
safe merge cleanup, no boilerplate
|
|
30
30
|
|
|
31
|
+
See `docscribe --help` for type options (--rbs, --sig-dir, --rbs-collection, --[no-]validate-types).
|
|
32
|
+
|
|
31
33
|
TEXT
|
|
32
34
|
|
|
33
35
|
class << self
|
|
@@ -35,14 +37,15 @@ module Docscribe
|
|
|
35
37
|
# @return [Integer]
|
|
36
38
|
def run(argv)
|
|
37
39
|
options = parse_options(argv)
|
|
38
|
-
|
|
40
|
+
target = options[:dir]
|
|
41
|
+
@extra_argv = options[:extra_argv]
|
|
39
42
|
|
|
40
43
|
announce_start
|
|
41
44
|
|
|
42
|
-
exit1 = run_first_pass(
|
|
45
|
+
exit1 = run_first_pass(target)
|
|
43
46
|
return exit1 unless exit1.zero?
|
|
44
47
|
|
|
45
|
-
exit2 = run_second_pass(
|
|
48
|
+
exit2 = run_second_pass(target)
|
|
46
49
|
return exit2 unless exit2.zero?
|
|
47
50
|
|
|
48
51
|
announce_complete
|
|
@@ -54,13 +57,28 @@ module Docscribe
|
|
|
54
57
|
# @private
|
|
55
58
|
# @param [Array<String>] argv
|
|
56
59
|
# @return [Hash<Symbol, Object>]
|
|
57
|
-
def parse_options(argv)
|
|
58
|
-
options = { dir: '.' }
|
|
60
|
+
def parse_options(argv) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
|
61
|
+
options = { dir: '.', extra_argv: [] }
|
|
62
|
+
extra_argv = []
|
|
59
63
|
OptionParser.new(BANNER) do |opts|
|
|
60
64
|
opts.on('-h', '--help', 'Show this help') { puts opts or exit 0 }
|
|
65
|
+
opts.on('--[no-]rbs', 'Use RBS signatures when available') do |v|
|
|
66
|
+
extra_argv << (v ? '--rbs' : '--no-rbs')
|
|
67
|
+
end
|
|
68
|
+
opts.on('--sig-dir DIR', 'Add an RBS signature directory (repeatable). Implies --rbs.') do |v|
|
|
69
|
+
extra_argv << '--sig-dir' << v
|
|
70
|
+
end
|
|
71
|
+
opts.on('--rbs-collection', 'Auto-discover RBS collection from rbs_collection.lock.yaml. Implies --rbs.') do
|
|
72
|
+
extra_argv << '--rbs-collection'
|
|
73
|
+
end
|
|
74
|
+
opts.on('--[no-]validate-types', 'Validate YARD types against inferred/RBS types (optional, default: off)') do |v|
|
|
75
|
+
extra_argv << (v ? '--validate-types' : '--no-validate-types')
|
|
76
|
+
end
|
|
61
77
|
opts.parse!(argv)
|
|
62
78
|
end
|
|
63
79
|
options[:dir] = argv.first if argv.any?
|
|
80
|
+
options[:extra_argv] = extra_argv
|
|
81
|
+
@extra_argv = extra_argv
|
|
64
82
|
options
|
|
65
83
|
end
|
|
66
84
|
|
|
@@ -72,23 +90,49 @@ module Docscribe
|
|
|
72
90
|
end
|
|
73
91
|
|
|
74
92
|
# @private
|
|
75
|
-
# @param [String]
|
|
93
|
+
# @param [String] target
|
|
76
94
|
# @return [Integer]
|
|
77
|
-
def run_first_pass(
|
|
78
|
-
|
|
79
|
-
|
|
95
|
+
def run_first_pass(target) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
96
|
+
dir_for_flag = File.file?(target) ? File.dirname(target) : target
|
|
97
|
+
has_collection = File.exist?(File.join(dir_for_flag, 'rbs_collection.lock.yaml')) || File.exist?('rbs_collection.lock.yaml')
|
|
98
|
+
flag = has_collection ? '--rbs-collection' : '--rbs'
|
|
99
|
+
extra = @extra_argv || []
|
|
100
|
+
filtered = extra.reject { |a| a == '--no-rbs' }
|
|
101
|
+
has_rbs_flag = extra.any? { |a| %w[--rbs --rbs-collection --no-rbs].include?(a) }
|
|
102
|
+
has_no_rbs = extra.include?('--no-rbs')
|
|
103
|
+
argv1 = ['-AkB']
|
|
104
|
+
argv1.concat(filtered)
|
|
105
|
+
if has_rbs_flag
|
|
106
|
+
argv1 << dir_for_flag unless has_no_rbs || argv1.include?(dir_for_flag)
|
|
107
|
+
else
|
|
108
|
+
argv1 << flag << dir_for_flag
|
|
109
|
+
end
|
|
110
|
+
puts "Pass 1: Aggressive rebuild with #{has_collection ? 'RBS collection' : 'RBS'}#{' + validate-types' if extra.include?('--validate-types')}..."
|
|
80
111
|
options1 = Docscribe::CLI::Options.parse!(argv1)
|
|
81
|
-
Docscribe::CLI::Run.run(options: options1, argv: [
|
|
112
|
+
Docscribe::CLI::Run.run(options: options1, argv: [target])
|
|
82
113
|
end
|
|
83
114
|
|
|
84
115
|
# @private
|
|
85
|
-
# @param [String]
|
|
116
|
+
# @param [String] target
|
|
86
117
|
# @return [Integer]
|
|
87
|
-
def run_second_pass(
|
|
88
|
-
|
|
89
|
-
|
|
118
|
+
def run_second_pass(target) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
119
|
+
dir_for_flag = File.file?(target) ? File.dirname(target) : target
|
|
120
|
+
has_collection = File.exist?(File.join(dir_for_flag, 'rbs_collection.lock.yaml')) || File.exist?('rbs_collection.lock.yaml')
|
|
121
|
+
flag = has_collection ? '--rbs-collection' : '--rbs'
|
|
122
|
+
extra = @extra_argv || []
|
|
123
|
+
filtered = extra.reject { |a| a == '--no-rbs' }
|
|
124
|
+
has_rbs_flag = extra.any? { |a| %w[--rbs --rbs-collection --no-rbs].include?(a) }
|
|
125
|
+
has_no_rbs = extra.include?('--no-rbs')
|
|
126
|
+
argv2 = ['-aB']
|
|
127
|
+
argv2.concat(filtered)
|
|
128
|
+
if has_rbs_flag
|
|
129
|
+
argv2 << dir_for_flag unless has_no_rbs || argv2.include?(dir_for_flag)
|
|
130
|
+
else
|
|
131
|
+
argv2 << flag << dir_for_flag
|
|
132
|
+
end
|
|
133
|
+
puts "Pass 2: Safe merge with #{has_collection ? 'RBS collection' : 'RBS'}#{' + validate-types' if extra.include?('--validate-types')}..."
|
|
90
134
|
options2 = Docscribe::CLI::Options.parse!(argv2)
|
|
91
|
-
Docscribe::CLI::Run.run(options: options2, argv: [
|
|
135
|
+
Docscribe::CLI::Run.run(options: options2, argv: [target])
|
|
92
136
|
end
|
|
93
137
|
|
|
94
138
|
# @private
|
data/lib/docscribe/cli.rb
CHANGED
|
@@ -6,6 +6,24 @@ require 'docscribe/cli/run'
|
|
|
6
6
|
module Docscribe
|
|
7
7
|
# CLI entry point and command dispatch.
|
|
8
8
|
module CLI
|
|
9
|
+
COMMANDS = {
|
|
10
|
+
'check_for_comments' => :CheckForComments,
|
|
11
|
+
'config' => :ConfigDump,
|
|
12
|
+
'coverage' => :Coverage,
|
|
13
|
+
'generate' => :Generate,
|
|
14
|
+
'init' => :Init,
|
|
15
|
+
'rbs' => :RbsGen,
|
|
16
|
+
'server' => :ServerCmd,
|
|
17
|
+
'sigs' => :Sigs,
|
|
18
|
+
'update_types' => :UpdateTypes
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
# Subcommands whose file name differs from the command name.
|
|
22
|
+
SUBCOMMAND_FILES = {
|
|
23
|
+
'config' => 'config_dump',
|
|
24
|
+
'rbs' => 'rbs_gen'
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
9
27
|
class << self
|
|
10
28
|
# @param [Array<String>] argv
|
|
11
29
|
# @return [Integer]
|
|
@@ -17,18 +35,6 @@ module Docscribe
|
|
|
17
35
|
Docscribe::CLI::Run.run(options: options, argv: argv)
|
|
18
36
|
end
|
|
19
37
|
|
|
20
|
-
COMMANDS = {
|
|
21
|
-
'check_for_comments' => :CheckForComments,
|
|
22
|
-
'config' => :ConfigDump,
|
|
23
|
-
'coverage' => :Coverage,
|
|
24
|
-
'generate' => :Generate,
|
|
25
|
-
'init' => :Init,
|
|
26
|
-
'rbs' => :RbsGen,
|
|
27
|
-
'server' => :ServerCmd,
|
|
28
|
-
'sigs' => :Sigs,
|
|
29
|
-
'update_types' => :UpdateTypes
|
|
30
|
-
}.freeze
|
|
31
|
-
|
|
32
38
|
private
|
|
33
39
|
|
|
34
40
|
# @private
|
|
@@ -46,7 +52,7 @@ module Docscribe
|
|
|
46
52
|
const_name = COMMANDS[cmd]
|
|
47
53
|
return 0 unless const_name
|
|
48
54
|
|
|
49
|
-
require "docscribe/cli/#{cmd
|
|
55
|
+
require "docscribe/cli/#{SUBCOMMAND_FILES.fetch(cmd) { cmd }}"
|
|
50
56
|
Docscribe::CLI.const_get(const_name).run(argv)
|
|
51
57
|
end
|
|
52
58
|
end
|
data/lib/docscribe/config/rbs.rb
CHANGED
|
@@ -107,10 +107,31 @@ module Docscribe
|
|
|
107
107
|
# RBS environment errors (e.g. duplicate declarations against core
|
|
108
108
|
# stdlib types) do not silence all RBS lookups.
|
|
109
109
|
#
|
|
110
|
+
# When no explicit dirs are configured but `rbs.collection: true` is
|
|
111
|
+
# set, the lock file is auto-discovered (same as `--rbs-collection`),
|
|
112
|
+
# so plain `check` gets the full environment without extra flags.
|
|
113
|
+
#
|
|
110
114
|
# @private
|
|
111
115
|
# @return [Array<String>]
|
|
112
116
|
def rbs_collection_dirs
|
|
113
|
-
Array(raw.dig('rbs', 'collection_dirs')).map(&:to_s) # steep:ignore
|
|
117
|
+
explicit = Array(raw.dig('rbs', 'collection_dirs')).map(&:to_s) # steep:ignore
|
|
118
|
+
return explicit unless explicit.empty?
|
|
119
|
+
return [] unless raw.dig('rbs', 'collection')
|
|
120
|
+
|
|
121
|
+
Array(discovered_collection_dir).compact
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Resolve the collection directory from rbs_collection.lock.yaml.
|
|
125
|
+
#
|
|
126
|
+
# @private
|
|
127
|
+
# @raise [LoadError]
|
|
128
|
+
# @return [String, nil] resolved directory or nil when no lock file
|
|
129
|
+
# @return [nil] if LoadError
|
|
130
|
+
def discovered_collection_dir
|
|
131
|
+
require 'docscribe/types/rbs/collection_loader'
|
|
132
|
+
Docscribe::Types::RBS::CollectionLoader.resolve
|
|
133
|
+
rescue LoadError
|
|
134
|
+
nil
|
|
114
135
|
end
|
|
115
136
|
|
|
116
137
|
# Whether generic RBS types should be collapsed to simpler container names.
|
|
@@ -106,6 +106,9 @@ module Docscribe
|
|
|
106
106
|
# Skip @param for anonymous block arguments (&) (Ruby 3.2+)
|
|
107
107
|
skip_anonymous_block_params: false
|
|
108
108
|
|
|
109
|
+
# Validate YARD types against inferred/RBS types
|
|
110
|
+
validate_types: false
|
|
111
|
+
|
|
109
112
|
plugins:
|
|
110
113
|
# Load custom plugins
|
|
111
114
|
# Example:
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
# Validation-related configuration.
|
|
5
|
+
class Config
|
|
6
|
+
# Whether to validate YARD types against inferred / external types.
|
|
7
|
+
#
|
|
8
|
+
# When enabled, `docscribe lib --validate-types` (or `validate_types: true`
|
|
9
|
+
# in `docscribe.yml`) compares each `@param`/`@return` type written in YARD
|
|
10
|
+
# against the type inferred from the method body or provided via RBS/Sorbet.
|
|
11
|
+
# Mismatches are reported as `updated_param` / `updated_return` and, in
|
|
12
|
+
# check mode, cause exit 1.
|
|
13
|
+
#
|
|
14
|
+
# @return [Boolean]
|
|
15
|
+
def validate_types?
|
|
16
|
+
fetch_bool(%w[validate_types], false)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/docscribe/config.rb
CHANGED
|
@@ -8,9 +8,9 @@ module Docscribe
|
|
|
8
8
|
insert update_all delete_all].freeze
|
|
9
9
|
|
|
10
10
|
class << self
|
|
11
|
-
# @param [
|
|
12
|
-
# @param [
|
|
13
|
-
# @return [Hash<Symbol,
|
|
11
|
+
# @param [Parser::AST::Node, nil] body
|
|
12
|
+
# @param [Symbol?] method_name
|
|
13
|
+
# @return [Hash<Symbol, Boolean, String, nil>]
|
|
14
14
|
def analyze(body, method_name)
|
|
15
15
|
result = default_result(method_name)
|
|
16
16
|
|
|
@@ -20,8 +20,8 @@ module Docscribe
|
|
|
20
20
|
result
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
# @param [
|
|
24
|
-
# @return [Hash<Symbol,
|
|
23
|
+
# @param [Symbol?] method_name
|
|
24
|
+
# @return [Hash<Symbol, Boolean, String, nil>]
|
|
25
25
|
def default_result(method_name)
|
|
26
26
|
{
|
|
27
27
|
predicate: method_name&.to_s&.end_with?('?') || false,
|
|
@@ -33,8 +33,8 @@ module Docscribe
|
|
|
33
33
|
}
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
# @param [Hash<Symbol,
|
|
37
|
-
# @param [
|
|
36
|
+
# @param [Hash<Symbol, Boolean, String, nil>] analysis
|
|
37
|
+
# @param [Symbol?] _method_name
|
|
38
38
|
# @return [String?]
|
|
39
39
|
def infer_description(analysis, _method_name)
|
|
40
40
|
return nil unless analysis[:has_side_effects] || analysis[:predicate]
|
|
@@ -51,8 +51,8 @@ module Docscribe
|
|
|
51
51
|
private
|
|
52
52
|
|
|
53
53
|
# @private
|
|
54
|
-
# @param [
|
|
55
|
-
# @param [Hash<Symbol,
|
|
54
|
+
# @param [Parser::AST::Node] node
|
|
55
|
+
# @param [Hash<Symbol, Boolean, String, nil>] result
|
|
56
56
|
# @return [void]
|
|
57
57
|
def analyze_body(node, result)
|
|
58
58
|
case node.type
|
|
@@ -68,8 +68,8 @@ module Docscribe
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
# @private
|
|
71
|
-
# @param [
|
|
72
|
-
# @param [Hash<Symbol,
|
|
71
|
+
# @param [Parser::AST::Node] node
|
|
72
|
+
# @param [Hash<Symbol, Boolean, String, nil>] result
|
|
73
73
|
# @return [void]
|
|
74
74
|
def recurse_children(node, result)
|
|
75
75
|
node.children.each do |child|
|
|
@@ -78,8 +78,8 @@ module Docscribe
|
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
# @private
|
|
81
|
-
# @param [
|
|
82
|
-
# @param [Hash<Symbol,
|
|
81
|
+
# @param [Parser::AST::Node] node
|
|
82
|
+
# @param [Hash<Symbol, Boolean, String, nil>] result
|
|
83
83
|
# @return [void]
|
|
84
84
|
def analyze_send(node, result)
|
|
85
85
|
_receiver, method_name = *node
|
|
@@ -33,8 +33,8 @@ module Docscribe
|
|
|
33
33
|
# @return [String, nil]
|
|
34
34
|
def prefix_param_type(name)
|
|
35
35
|
return 'Array' if name.start_with?('*') && !name.start_with?('**')
|
|
36
|
-
return 'Hash'
|
|
37
|
-
return 'Proc'
|
|
36
|
+
return 'Hash' if name.start_with?('**')
|
|
37
|
+
return 'Proc' if name.start_with?('&')
|
|
38
38
|
|
|
39
39
|
nil
|
|
40
40
|
end
|
|
@@ -23,10 +23,9 @@ module Docscribe
|
|
|
23
23
|
raises = [] #: Array[String]
|
|
24
24
|
|
|
25
25
|
ASTWalk.walk(node) do |n|
|
|
26
|
-
|
|
27
|
-
when :resbody
|
|
26
|
+
if n&.type == :resbody
|
|
28
27
|
raises.concat(exception_names_from_rescue_list(n.children[0]))
|
|
29
|
-
|
|
28
|
+
elsif n&.type == :send
|
|
30
29
|
collect_send_raise(raises, n)
|
|
31
30
|
end
|
|
32
31
|
end
|
|
@@ -45,9 +44,9 @@ module Docscribe
|
|
|
45
44
|
# @param [Parser::AST::Node, nil] exc_list rescue exception list node
|
|
46
45
|
# @return [Array<String>]
|
|
47
46
|
def exception_names_from_rescue_list(exc_list)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
return [DEFAULT_ERROR] unless exc_list.is_a?(Parser::AST::Node)
|
|
48
|
+
|
|
49
|
+
if exc_list.type == :array
|
|
51
50
|
exc_list.children.map { |e| Names.const_full_name(e) || DEFAULT_ERROR }
|
|
52
51
|
else
|
|
53
52
|
[Names.const_full_name(exc_list) || DEFAULT_ERROR]
|