docscribe 1.4.2 → 1.5.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.
- checksums.yaml +4 -4
- data/README.md +601 -139
- data/exe/docscribe-client +105 -0
- data/lib/docscribe/cli/check_for_comments.rb +183 -0
- data/lib/docscribe/cli/config_builder.rb +107 -53
- data/lib/docscribe/cli/formatters/json.rb +294 -0
- data/lib/docscribe/cli/formatters/sarif.rb +235 -0
- data/lib/docscribe/cli/formatters/text.rb +208 -0
- data/lib/docscribe/cli/formatters.rb +26 -0
- data/lib/docscribe/cli/generate.rb +56 -62
- data/lib/docscribe/cli/init.rb +14 -6
- data/lib/docscribe/cli/options.rb +206 -89
- data/lib/docscribe/cli/rbs_gen.rb +529 -0
- data/lib/docscribe/cli/run.rb +433 -154
- data/lib/docscribe/cli/server.rb +135 -0
- data/lib/docscribe/cli/sigs.rb +366 -0
- data/lib/docscribe/cli/update_types.rb +103 -0
- data/lib/docscribe/cli.rb +21 -24
- data/lib/docscribe/config/defaults.rb +7 -2
- data/lib/docscribe/config/emit.rb +17 -0
- data/lib/docscribe/config/filtering.rb +17 -24
- data/lib/docscribe/config/loader.rb +19 -17
- data/lib/docscribe/config/plugin.rb +1 -1
- data/lib/docscribe/config/rbs.rb +39 -7
- data/lib/docscribe/config/sorbet.rb +22 -16
- data/lib/docscribe/config/sorting.rb +1 -1
- data/lib/docscribe/config/template.rb +10 -1
- data/lib/docscribe/config/utils.rb +11 -9
- data/lib/docscribe/config.rb +10 -6
- data/lib/docscribe/infer/ast_walk.rb +1 -1
- data/lib/docscribe/infer/literals.rb +6 -11
- data/lib/docscribe/infer/names.rb +2 -3
- data/lib/docscribe/infer/params.rb +14 -16
- data/lib/docscribe/infer/raises.rb +3 -5
- data/lib/docscribe/infer/returns.rb +615 -151
- data/lib/docscribe/infer.rb +29 -26
- data/lib/docscribe/inline_rewriter/collector.rb +159 -164
- data/lib/docscribe/inline_rewriter/doc_block.rb +145 -115
- data/lib/docscribe/inline_rewriter/doc_builder.rb +1032 -723
- data/lib/docscribe/inline_rewriter/source_helpers.rb +48 -48
- data/lib/docscribe/inline_rewriter/tag_sorter.rb +82 -85
- data/lib/docscribe/inline_rewriter.rb +485 -488
- data/lib/docscribe/lru_cache.rb +49 -0
- data/lib/docscribe/parsing.rb +28 -9
- data/lib/docscribe/plugin/base/collector_plugin.rb +2 -1
- data/lib/docscribe/plugin/base/tag_plugin.rb +0 -1
- data/lib/docscribe/plugin/context.rb +28 -18
- data/lib/docscribe/plugin/registry.rb +25 -26
- data/lib/docscribe/plugin/tag.rb +9 -14
- data/lib/docscribe/plugin.rb +17 -16
- data/lib/docscribe/server.rb +608 -0
- data/lib/docscribe/types/provider_chain.rb +4 -2
- data/lib/docscribe/types/rbs/collection_loader.rb +2 -2
- data/lib/docscribe/types/rbs/provider.rb +177 -51
- data/lib/docscribe/types/rbs/type_formatter.rb +224 -83
- data/lib/docscribe/types/signature.rb +22 -42
- data/lib/docscribe/types/sorbet/base_provider.rb +29 -21
- data/lib/docscribe/types/sorbet/rbi_provider.rb +6 -5
- data/lib/docscribe/types/sorbet/source_provider.rb +6 -4
- data/lib/docscribe/types/yard/formatter.rb +100 -0
- data/lib/docscribe/types/yard/parser.rb +240 -0
- data/lib/docscribe/types/yard/types.rb +52 -0
- data/lib/docscribe/version.rb +1 -1
- metadata +38 -1
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
|
|
5
|
+
module Docscribe
|
|
6
|
+
module CLI
|
|
7
|
+
# Handle the `docscribe server` subcommand.
|
|
8
|
+
module ServerCmd
|
|
9
|
+
BANNER = <<~TEXT
|
|
10
|
+
Usage: docscribe server <command> [options]
|
|
11
|
+
|
|
12
|
+
Commands:
|
|
13
|
+
start Start the background daemon
|
|
14
|
+
stop Stop the background daemon
|
|
15
|
+
status Show daemon status
|
|
16
|
+
|
|
17
|
+
Options:
|
|
18
|
+
-C, --config <path> Config file path (default: auto-detect)
|
|
19
|
+
|
|
20
|
+
Once the server is running, use `--server` with other commands:
|
|
21
|
+
docscribe --server check lib/
|
|
22
|
+
docscribe --server --autocorrect lib/
|
|
23
|
+
TEXT
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
# Run the server subcommand.
|
|
27
|
+
#
|
|
28
|
+
# @param [Array<String>] argv subcommand arguments
|
|
29
|
+
# @return [Integer] exit code
|
|
30
|
+
def run(argv)
|
|
31
|
+
config_path, cmd = parse_args(argv)
|
|
32
|
+
return warn(BANNER) || 1 unless cmd
|
|
33
|
+
|
|
34
|
+
case cmd
|
|
35
|
+
when 'start' then start_server(config_path)
|
|
36
|
+
when 'stop' then stop_server(config_path)
|
|
37
|
+
when 'status' then show_status(config_path)
|
|
38
|
+
else warn(usage) || 1
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
# @private
|
|
45
|
+
# @param [Array<String>] argv
|
|
46
|
+
# @return [(String?, String?)]
|
|
47
|
+
def parse_args(argv)
|
|
48
|
+
config_path = nil
|
|
49
|
+
rest = OptionParser.new do |opts|
|
|
50
|
+
opts.on('-C', '--config <path>', 'Config file path') { |v| config_path = v }
|
|
51
|
+
end.parse!(argv.dup)
|
|
52
|
+
[config_path, rest.first]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Start the background daemon process.
|
|
56
|
+
#
|
|
57
|
+
# @private
|
|
58
|
+
# @param [String?] config_path optional config file path
|
|
59
|
+
# @return [Integer] exit code
|
|
60
|
+
def start_server(config_path = nil)
|
|
61
|
+
require 'docscribe/server'
|
|
62
|
+
return already_running(config_path) if Docscribe::Server.running?(config_path)
|
|
63
|
+
|
|
64
|
+
Docscribe::Server.ensure_running!(daemonize: true, config_path: config_path)
|
|
65
|
+
pid = Docscribe::Server.read_pid(config_path)
|
|
66
|
+
warn "Docscribe server started (pid #{pid})"
|
|
67
|
+
0
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @private
|
|
71
|
+
# @param [String?] config_path optional config file path
|
|
72
|
+
# @return [Integer]
|
|
73
|
+
def already_running(config_path = nil)
|
|
74
|
+
pid = Docscribe::Server.read_pid(config_path)
|
|
75
|
+
warn "Docscribe server already running (pid #{pid})"
|
|
76
|
+
0
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Stop the background daemon.
|
|
80
|
+
#
|
|
81
|
+
# @private
|
|
82
|
+
# @param [String?] config_path optional config file path
|
|
83
|
+
# @return [Integer] exit code
|
|
84
|
+
def stop_server(config_path = nil)
|
|
85
|
+
require 'docscribe/server'
|
|
86
|
+
alive = Docscribe::Server::Client.new(config_path: config_path).shutdown
|
|
87
|
+
warn(alive ? 'Docscribe server stopped' : 'Docscribe server is not running')
|
|
88
|
+
0
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Show the server status.
|
|
92
|
+
#
|
|
93
|
+
# @private
|
|
94
|
+
# @param [String?] config_path optional config file path
|
|
95
|
+
# @return [Integer] exit code
|
|
96
|
+
def show_status(config_path = nil)
|
|
97
|
+
require 'docscribe/server'
|
|
98
|
+
return warn('Docscribe server is not running') || 0 unless Docscribe::Server.running?(config_path)
|
|
99
|
+
|
|
100
|
+
info = Docscribe::Server::Client.new(config_path: config_path).ping
|
|
101
|
+
info ? show_status_from_ping(info) : show_status_fallback(config_path)
|
|
102
|
+
0
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @private
|
|
106
|
+
# @param [Hash<String, Object>] info ping response hash
|
|
107
|
+
# @return [void]
|
|
108
|
+
def show_status_from_ping(info)
|
|
109
|
+
pid = info.dig('result', 'pid')
|
|
110
|
+
version = info.dig('result', 'version')
|
|
111
|
+
uptime = info.dig('result', 'uptime')
|
|
112
|
+
sock = info.dig('result', 'socket_path')
|
|
113
|
+
warn "Docscribe server v#{version} is running (pid #{pid}, socket #{sock}, uptime #{uptime}s)"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @private
|
|
117
|
+
# @param [String?] config_path
|
|
118
|
+
# @return [void]
|
|
119
|
+
def show_status_fallback(config_path)
|
|
120
|
+
pid = Docscribe::Server.read_pid(config_path)
|
|
121
|
+
sock = Docscribe::Server.socket_path(config_path)
|
|
122
|
+
warn "Docscribe server is running (pid #{pid}, socket #{sock})"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Print usage information for the server subcommand.
|
|
126
|
+
#
|
|
127
|
+
# @private
|
|
128
|
+
# @return [String]
|
|
129
|
+
def usage
|
|
130
|
+
BANNER
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require 'docscribe/parsing'
|
|
5
|
+
require 'docscribe/types/rbs/provider'
|
|
6
|
+
|
|
7
|
+
module Docscribe
|
|
8
|
+
module CLI
|
|
9
|
+
# Check RBS signature coverage for Ruby source files.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# docscribe sigs [options] [files...]
|
|
13
|
+
#
|
|
14
|
+
# Parses Ruby source files, extracts method definitions, and checks
|
|
15
|
+
# each method against the configured RBS signature directories.
|
|
16
|
+
# Reports methods that lack RBS type signatures.
|
|
17
|
+
module Sigs
|
|
18
|
+
BANNER = <<~TEXT
|
|
19
|
+
Usage: docscribe sigs [options] [files...]
|
|
20
|
+
|
|
21
|
+
Check RBS signature coverage for Ruby source files.
|
|
22
|
+
|
|
23
|
+
TEXT
|
|
24
|
+
|
|
25
|
+
EXIT_CODES = "\nExit codes:\n " \
|
|
26
|
+
"0 - all methods have signatures\n " \
|
|
27
|
+
"1 - some methods lack signatures\n " \
|
|
28
|
+
'2 - error occurred'
|
|
29
|
+
|
|
30
|
+
# @!attribute [rw] name
|
|
31
|
+
# @return [Symbol]
|
|
32
|
+
# @param [Symbol] value
|
|
33
|
+
#
|
|
34
|
+
# @!attribute [rw] scope
|
|
35
|
+
# @return [Symbol]
|
|
36
|
+
# @param [Symbol] value
|
|
37
|
+
#
|
|
38
|
+
# @!attribute [rw] container
|
|
39
|
+
# @return [String?]
|
|
40
|
+
# @param [String?] value
|
|
41
|
+
#
|
|
42
|
+
# @!attribute [rw] file
|
|
43
|
+
# @return [String]
|
|
44
|
+
# @param [String] value
|
|
45
|
+
#
|
|
46
|
+
# @!attribute [rw] line
|
|
47
|
+
# @return [Integer]
|
|
48
|
+
# @param [Integer] value
|
|
49
|
+
MethodDef = Struct.new(:name, :scope, :container, :file, :line, keyword_init: true)
|
|
50
|
+
|
|
51
|
+
class << self
|
|
52
|
+
# @param [Array<String>] argv
|
|
53
|
+
# @return [Integer]
|
|
54
|
+
def run(argv)
|
|
55
|
+
warn_ruby_version
|
|
56
|
+
options = parse_options(argv)
|
|
57
|
+
paths = expand_paths(argv)
|
|
58
|
+
return no_files_found if paths.empty?
|
|
59
|
+
|
|
60
|
+
run_with(options, extract_methods(paths))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# @private
|
|
66
|
+
# @return [void]
|
|
67
|
+
def warn_ruby_version
|
|
68
|
+
return unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.0')
|
|
69
|
+
|
|
70
|
+
warn 'Warning: docscribe sigs requires Ruby 3.0+ for RBS support. ' \
|
|
71
|
+
"You are running Ruby #{RUBY_VERSION}."
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @private
|
|
75
|
+
# @param [Array<String>] argv
|
|
76
|
+
# @return [Hash<Symbol, Object>]
|
|
77
|
+
def parse_options(argv)
|
|
78
|
+
options = { sig_dirs: ['sig'], rbs_collection: false, verbose: false }
|
|
79
|
+
|
|
80
|
+
parser = OptionParser.new do |opts|
|
|
81
|
+
opts.banner = BANNER
|
|
82
|
+
register_sig_options(opts, options)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
parser.parse!(argv)
|
|
86
|
+
options
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @private
|
|
90
|
+
# @param [OptionParser] opts
|
|
91
|
+
# @param [Hash<Symbol, Object>] options
|
|
92
|
+
# @return [void]
|
|
93
|
+
def register_sig_options(opts, options)
|
|
94
|
+
opts.on('-s', '--sig-dir DIR', 'Add RBS signature directory (repeatable)') { |d| options[:sig_dirs] << d }
|
|
95
|
+
opts.on('--rbs-collection', 'Use RBS collection') { options[:rbs_collection] = true }
|
|
96
|
+
opts.on('--verbose', 'Print methods that have signatures too') { options[:verbose] = true }
|
|
97
|
+
opts.on('-h', '--help', 'Show this help') do
|
|
98
|
+
puts opts, EXIT_CODES
|
|
99
|
+
exit 0
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @private
|
|
104
|
+
# @param [Array<String>] args
|
|
105
|
+
# @return [Array<String>]
|
|
106
|
+
def expand_paths(args)
|
|
107
|
+
files = [] #: Array[String]
|
|
108
|
+
args = ['.'] if args.empty?
|
|
109
|
+
args.each { |path| expand_single_path(files, path) }
|
|
110
|
+
files.uniq.sort
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @private
|
|
114
|
+
# @param [Array<String>] files
|
|
115
|
+
# @param [String] path
|
|
116
|
+
# @return [void]
|
|
117
|
+
def expand_single_path(files, path)
|
|
118
|
+
if File.directory?(path)
|
|
119
|
+
files.concat(Dir.glob(File.join(path, '**', '*.rb')))
|
|
120
|
+
elsif File.file?(path)
|
|
121
|
+
files << path
|
|
122
|
+
else
|
|
123
|
+
warn "Skipping missing path: #{path}"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @private
|
|
128
|
+
# @return [Integer]
|
|
129
|
+
def no_files_found
|
|
130
|
+
warn 'No files found. Pass files or directories (e.g. `docscribe sigs lib`).'
|
|
131
|
+
2
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# @private
|
|
135
|
+
# @param [Hash<Symbol, Object>] options
|
|
136
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
137
|
+
# @return [Integer]
|
|
138
|
+
def run_with(options, methods)
|
|
139
|
+
return 0 if methods.empty?
|
|
140
|
+
|
|
141
|
+
provider = build_provider(options)
|
|
142
|
+
return 2 unless provider
|
|
143
|
+
|
|
144
|
+
missing = check_sigs(methods, provider, verbose: options[:verbose])
|
|
145
|
+
report_results(methods, missing)
|
|
146
|
+
missing.empty? ? 0 : 1
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# @private
|
|
150
|
+
# @param [Array<String>] paths
|
|
151
|
+
# @return [Array<Docscribe::CLI::Sigs::MethodDef>]
|
|
152
|
+
def extract_methods(paths)
|
|
153
|
+
methods = [] #: Array[MethodDef]
|
|
154
|
+
paths.each { |path| extract_methods_from_file(path, methods) }
|
|
155
|
+
methods
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @private
|
|
159
|
+
# @param [String] path
|
|
160
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
161
|
+
# @raise [Parser::SyntaxError]
|
|
162
|
+
# @raise [StandardError]
|
|
163
|
+
# @return [void]
|
|
164
|
+
# @return [nil] if Parser::SyntaxError
|
|
165
|
+
# @return [nil] if StandardError
|
|
166
|
+
def extract_methods_from_file(path, methods)
|
|
167
|
+
src = File.read(path)
|
|
168
|
+
ast = Docscribe::Parsing.parse(src, file: path)
|
|
169
|
+
return unless ast
|
|
170
|
+
|
|
171
|
+
walk_for_methods(ast, [], methods, path)
|
|
172
|
+
rescue Parser::SyntaxError => e # steep:ignore
|
|
173
|
+
warn "Syntax error in #{path}: #{e.message}"
|
|
174
|
+
rescue StandardError => e
|
|
175
|
+
warn "Error parsing #{path}: #{e.class}: #{e.message}"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @private
|
|
179
|
+
# @param [Parser::AST::Node] node
|
|
180
|
+
# @param [Array<String>] containers
|
|
181
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
182
|
+
# @param [String] path
|
|
183
|
+
# @param [Boolean] inside_sclass
|
|
184
|
+
# @return [void]
|
|
185
|
+
def walk_for_methods(node, containers, methods, path, inside_sclass: false)
|
|
186
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
187
|
+
|
|
188
|
+
case node.type
|
|
189
|
+
when :class, :module then walk_class_module(node, containers, methods, path)
|
|
190
|
+
when :sclass then walk_sclass(node, containers, methods, path)
|
|
191
|
+
when :def then collect_def(node, containers, methods, path, inside_sclass: inside_sclass)
|
|
192
|
+
when :defs then collect_defs(node, containers, methods, path)
|
|
193
|
+
else walk_children(node, containers, methods, path, inside_sclass: inside_sclass)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# @private
|
|
198
|
+
# @param [Parser::AST::Node] node
|
|
199
|
+
# @param [Array<String>] containers
|
|
200
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
201
|
+
# @param [String] path
|
|
202
|
+
# @return [void]
|
|
203
|
+
def walk_class_module(node, containers, methods, path)
|
|
204
|
+
containers.push(const_name(node.children[0]))
|
|
205
|
+
node.children.drop(1).each { |c| walk_for_methods(c, containers, methods, path) }
|
|
206
|
+
containers.pop
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# @private
|
|
210
|
+
# @param [Parser::AST::Node] node
|
|
211
|
+
# @param [Array<String>] containers
|
|
212
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
213
|
+
# @param [String] path
|
|
214
|
+
# @return [void]
|
|
215
|
+
def walk_sclass(node, containers, methods, path)
|
|
216
|
+
node.children.drop(1).each { |c| walk_for_methods(c, containers, methods, path, inside_sclass: true) }
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# @private
|
|
220
|
+
# @param [Parser::AST::Node] node
|
|
221
|
+
# @param [Array<String>] containers
|
|
222
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
223
|
+
# @param [String] path
|
|
224
|
+
# @param [Boolean] inside_sclass
|
|
225
|
+
# @return [void]
|
|
226
|
+
def walk_children(node, containers, methods, path, inside_sclass: false)
|
|
227
|
+
node.children.each { |c| walk_for_methods(c, containers, methods, path, inside_sclass: inside_sclass) }
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# @private
|
|
231
|
+
# @param [Parser::AST::Node] node
|
|
232
|
+
# @param [Array<String>] containers
|
|
233
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
234
|
+
# @param [String] path
|
|
235
|
+
# @param [Boolean] inside_sclass
|
|
236
|
+
# @return [void]
|
|
237
|
+
def collect_def(node, containers, methods, path, inside_sclass: false)
|
|
238
|
+
methods << MethodDef.new(
|
|
239
|
+
name: node.children[0],
|
|
240
|
+
scope: inside_sclass ? :class : :instance,
|
|
241
|
+
container: container_name(containers),
|
|
242
|
+
file: path,
|
|
243
|
+
line: node.loc&.line || 1
|
|
244
|
+
)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# @private
|
|
248
|
+
# @param [Parser::AST::Node] node
|
|
249
|
+
# @param [Array<String>] containers
|
|
250
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
251
|
+
# @param [String] path
|
|
252
|
+
# @return [void]
|
|
253
|
+
def collect_defs(node, containers, methods, path)
|
|
254
|
+
methods << MethodDef.new(
|
|
255
|
+
name: node.children[1],
|
|
256
|
+
scope: :class,
|
|
257
|
+
container: container_name(containers),
|
|
258
|
+
file: path,
|
|
259
|
+
line: node.loc&.line || 1
|
|
260
|
+
)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# @private
|
|
264
|
+
# @param [Array<String>] containers
|
|
265
|
+
# @return [String?]
|
|
266
|
+
def container_name(containers)
|
|
267
|
+
containers.empty? ? nil : containers.join('::')
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# @private
|
|
271
|
+
# @param [Parser::AST::Node] node
|
|
272
|
+
# @return [String]
|
|
273
|
+
def const_name(node)
|
|
274
|
+
return node.to_s unless node.is_a?(Parser::AST::Node)
|
|
275
|
+
return node.children[1].to_s if node.type == :const
|
|
276
|
+
|
|
277
|
+
node.children.map { |c| c.is_a?(Parser::AST::Node) ? const_name(c) : c.to_s }.join('::')
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# @private
|
|
281
|
+
# @param [Hash<Symbol, Object>] options
|
|
282
|
+
# @raise [LoadError]
|
|
283
|
+
# @raise [StandardError]
|
|
284
|
+
# @return [Docscribe::Types::RBS::Provider?]
|
|
285
|
+
# @return [nil] if LoadError
|
|
286
|
+
# @return [nil] if StandardError
|
|
287
|
+
def build_provider(options)
|
|
288
|
+
dirs = options[:rbs_collection] ? load_collection_dirs : [] #: Array[String]
|
|
289
|
+
Docscribe::Types::RBS::Provider.new(sig_dirs: options[:sig_dirs], collection_dirs: dirs)
|
|
290
|
+
rescue LoadError
|
|
291
|
+
warn 'Docscribe: rbs gem is not installed. Add `gem "rbs"` to your Gemfile ' \
|
|
292
|
+
'or run `bundle exec rbs collection install`.'
|
|
293
|
+
nil
|
|
294
|
+
rescue StandardError => e
|
|
295
|
+
warn "Docscribe: Failed to initialize RBS provider: #{e.class}: #{e.message}"
|
|
296
|
+
nil
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# @private
|
|
300
|
+
# @raise [StandardError]
|
|
301
|
+
# @return [Array<String>]
|
|
302
|
+
# @return [Array] if StandardError
|
|
303
|
+
def load_collection_dirs
|
|
304
|
+
dir = Docscribe::Types::RBS::CollectionLoader.resolve
|
|
305
|
+
dir ? [dir] : []
|
|
306
|
+
rescue StandardError => e
|
|
307
|
+
warn "Docscribe: Failed to load RBS collection: #{e.class}: #{e.message}"
|
|
308
|
+
[]
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# @private
|
|
312
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
313
|
+
# @param [Docscribe::Types::RBS::Provider] provider
|
|
314
|
+
# @param [Boolean] verbose
|
|
315
|
+
# @return [Array<Docscribe::CLI::Sigs::MethodDef>]
|
|
316
|
+
def check_sigs(methods, provider, verbose:)
|
|
317
|
+
missing = [] #: Array[MethodDef]
|
|
318
|
+
methods.each do |m|
|
|
319
|
+
sig = lookup_signature(m, provider)
|
|
320
|
+
puts " OK #{format_method(m)} (#{m.file}:#{m.line})" if sig && verbose
|
|
321
|
+
puts " MISS #{format_method(m)} (#{m.file}:#{m.line})" unless sig
|
|
322
|
+
missing << m unless sig
|
|
323
|
+
end
|
|
324
|
+
missing
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# @private
|
|
328
|
+
# @param [Docscribe::CLI::Sigs::MethodDef] method_def
|
|
329
|
+
# @param [Docscribe::Types::RBS::Provider] provider
|
|
330
|
+
# @return [Object, nil]
|
|
331
|
+
def lookup_signature(method_def, provider)
|
|
332
|
+
container = method_def.container
|
|
333
|
+
return nil unless container
|
|
334
|
+
|
|
335
|
+
provider.signature_for(
|
|
336
|
+
container: container,
|
|
337
|
+
scope: method_def.scope,
|
|
338
|
+
name: method_def.name
|
|
339
|
+
)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# @private
|
|
343
|
+
# @param [Docscribe::CLI::Sigs::MethodDef] method_def
|
|
344
|
+
# @return [String]
|
|
345
|
+
def format_method(method_def)
|
|
346
|
+
prefix = method_def.scope == :class ? 'self.' : ''
|
|
347
|
+
container = method_def.container ? "#{method_def.container}#" : ''
|
|
348
|
+
"#{container}#{prefix}#{method_def.name}"
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# @private
|
|
352
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
353
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] missing
|
|
354
|
+
# @return [void]
|
|
355
|
+
def report_results(methods, missing)
|
|
356
|
+
puts
|
|
357
|
+
if missing.empty?
|
|
358
|
+
puts "Docscribe: All #{methods.size} methods have RBS signatures"
|
|
359
|
+
else
|
|
360
|
+
puts "Docscribe: #{missing.size}/#{methods.size} methods missing RBS signatures"
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
|
|
5
|
+
require 'docscribe/cli/options'
|
|
6
|
+
require 'docscribe/cli/run'
|
|
7
|
+
|
|
8
|
+
module Docscribe
|
|
9
|
+
module CLI
|
|
10
|
+
# Two-pass update: rebuild docs then re-merge with RBS types.
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# docscribe update_types [directory]
|
|
14
|
+
#
|
|
15
|
+
# Pass 1: `-AkB --rbs-collection <dir>` — aggressive rebuild, keep descriptions,
|
|
16
|
+
# no boilerplate, using RBS collection signatures.
|
|
17
|
+
# Pass 2: `-aB --rbs-collection <dir>` — safe merge cleanup, no boilerplate,
|
|
18
|
+
# using RBS collection signatures.
|
|
19
|
+
module UpdateTypes
|
|
20
|
+
BANNER = <<~TEXT
|
|
21
|
+
Usage: docscribe update_types [directory]
|
|
22
|
+
|
|
23
|
+
Two-pass type-aware documentation update.
|
|
24
|
+
|
|
25
|
+
Pass 1 (aggressive): docscribe -AkB --rbs-collection <dir>
|
|
26
|
+
rebuild doc blocks, keep descriptions, no boilerplate
|
|
27
|
+
|
|
28
|
+
Pass 2 (safe): docscribe -aB --rbs-collection <dir>
|
|
29
|
+
safe merge cleanup, no boilerplate
|
|
30
|
+
|
|
31
|
+
TEXT
|
|
32
|
+
|
|
33
|
+
class << self
|
|
34
|
+
# @param [Array<String>] argv
|
|
35
|
+
# @return [Integer]
|
|
36
|
+
def run(argv)
|
|
37
|
+
options = parse_options(argv)
|
|
38
|
+
dir = options[:dir]
|
|
39
|
+
|
|
40
|
+
announce_start
|
|
41
|
+
|
|
42
|
+
exit1 = run_first_pass(dir)
|
|
43
|
+
return exit1 unless exit1.zero?
|
|
44
|
+
|
|
45
|
+
exit2 = run_second_pass(dir)
|
|
46
|
+
return exit2 unless exit2.zero?
|
|
47
|
+
|
|
48
|
+
announce_complete
|
|
49
|
+
0
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# @private
|
|
55
|
+
# @param [Array<String>] argv
|
|
56
|
+
# @return [Hash<Symbol, Object>]
|
|
57
|
+
def parse_options(argv)
|
|
58
|
+
options = { dir: '.' }
|
|
59
|
+
OptionParser.new(BANNER) do |opts|
|
|
60
|
+
opts.on('-h', '--help', 'Show this help') { puts opts or exit 0 }
|
|
61
|
+
opts.parse!(argv)
|
|
62
|
+
end
|
|
63
|
+
options[:dir] = argv.first if argv.any?
|
|
64
|
+
options
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @private
|
|
68
|
+
# @return [void]
|
|
69
|
+
def announce_start
|
|
70
|
+
puts 'Docscribe: Running type-aware documentation update...'
|
|
71
|
+
puts
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @private
|
|
75
|
+
# @param [String] dir
|
|
76
|
+
# @return [Integer]
|
|
77
|
+
def run_first_pass(dir)
|
|
78
|
+
puts 'Pass 1: Aggressive rebuild with RBS collection...'
|
|
79
|
+
argv1 = ['-AkB', '--rbs-collection', dir]
|
|
80
|
+
options1 = Docscribe::CLI::Options.parse!(argv1)
|
|
81
|
+
Docscribe::CLI::Run.run(options: options1, argv: [dir])
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @private
|
|
85
|
+
# @param [String] dir
|
|
86
|
+
# @return [Integer]
|
|
87
|
+
def run_second_pass(dir)
|
|
88
|
+
puts 'Pass 2: Safe merge with RBS collection...'
|
|
89
|
+
argv2 = ['-aB', '--rbs-collection', dir]
|
|
90
|
+
options2 = Docscribe::CLI::Options.parse!(argv2)
|
|
91
|
+
Docscribe::CLI::Run.run(options: options2, argv: [dir])
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @private
|
|
95
|
+
# @return [void]
|
|
96
|
+
def announce_complete
|
|
97
|
+
puts
|
|
98
|
+
puts 'Docscribe: Type-aware documentation update complete.'
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
data/lib/docscribe/cli.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'docscribe/cli/init'
|
|
4
|
-
require 'docscribe/cli/generate'
|
|
5
3
|
require 'docscribe/cli/options'
|
|
6
4
|
require 'docscribe/cli/run'
|
|
7
5
|
|
|
@@ -9,15 +7,8 @@ module Docscribe
|
|
|
9
7
|
# CLI entry point and command dispatch.
|
|
10
8
|
module CLI
|
|
11
9
|
class << self
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# Dispatches:
|
|
15
|
-
# - `docscribe init ...` to the config-template generator
|
|
16
|
-
# - `docscribe generate ...` to the plugin skeleton generator
|
|
17
|
-
# - all other commands to the main option parser and runner
|
|
18
|
-
#
|
|
19
|
-
# @param [Array<String>] argv raw command-line arguments
|
|
20
|
-
# @return [Integer] process exit code
|
|
10
|
+
# @param [Array<String>] argv
|
|
11
|
+
# @return [Integer]
|
|
21
12
|
def run(argv)
|
|
22
13
|
argv = argv.dup
|
|
23
14
|
return dispatch_subcommand(argv) if subcommand?(argv.first)
|
|
@@ -26,29 +17,35 @@ module Docscribe
|
|
|
26
17
|
Docscribe::CLI::Run.run(options: options, argv: argv)
|
|
27
18
|
end
|
|
28
19
|
|
|
20
|
+
COMMANDS = {
|
|
21
|
+
'init' => :Init,
|
|
22
|
+
'generate' => :Generate,
|
|
23
|
+
'sigs' => :Sigs,
|
|
24
|
+
'rbs' => :RbsGen,
|
|
25
|
+
'update_types' => :UpdateTypes,
|
|
26
|
+
'check_for_comments' => :CheckForComments,
|
|
27
|
+
'server' => :ServerCmd
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
29
30
|
private
|
|
30
31
|
|
|
31
32
|
# @private
|
|
32
|
-
# @param [String] cmd
|
|
33
|
+
# @param [String?] cmd
|
|
33
34
|
# @return [Boolean]
|
|
34
35
|
def subcommand?(cmd)
|
|
35
|
-
|
|
36
|
+
COMMANDS.key?(cmd)
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
# @private
|
|
39
40
|
# @param [Array<String>] argv
|
|
40
|
-
# @return [Integer
|
|
41
|
+
# @return [Integer]
|
|
41
42
|
def dispatch_subcommand(argv)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
Docscribe::CLI::Generate.run(argv)
|
|
49
|
-
else
|
|
50
|
-
0
|
|
51
|
-
end
|
|
43
|
+
cmd = argv.shift
|
|
44
|
+
const_name = COMMANDS[cmd]
|
|
45
|
+
return 0 unless const_name
|
|
46
|
+
|
|
47
|
+
require "docscribe/cli/#{cmd == 'rbs' ? 'rbs_gen' : cmd}"
|
|
48
|
+
Docscribe::CLI.const_get(const_name).run(argv)
|
|
52
49
|
end
|
|
53
50
|
end
|
|
54
51
|
end
|
|
@@ -63,13 +63,18 @@ module Docscribe
|
|
|
63
63
|
'collection' => false,
|
|
64
64
|
'sig_dirs' => ['sig'],
|
|
65
65
|
'collection_dirs' => [], #: Array[String]
|
|
66
|
-
'collapse_generics' => false
|
|
66
|
+
'collapse_generics' => false,
|
|
67
|
+
'warn_missing_collection' => true,
|
|
68
|
+
'collapse_object_generics' => false
|
|
67
69
|
},
|
|
68
70
|
'sorbet' => {
|
|
69
71
|
'enabled' => false,
|
|
70
72
|
'rbi_dirs' => ['sorbet/rbi', 'rbi'],
|
|
71
|
-
'collapse_generics' => false
|
|
73
|
+
'collapse_generics' => false,
|
|
74
|
+
'collapse_object_generics' => false
|
|
72
75
|
},
|
|
76
|
+
'keep_descriptions' => false,
|
|
77
|
+
'skip_anonymous_block_params' => false,
|
|
73
78
|
'plugins' => {
|
|
74
79
|
'require' => [] #: Array[String]
|
|
75
80
|
}
|