docscribe 1.1.0 → 1.2.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 +662 -187
- data/exe/docscribe +2 -126
- data/lib/docscribe/cli/config_builder.rb +62 -0
- data/lib/docscribe/cli/init.rb +58 -0
- data/lib/docscribe/cli/options.rb +204 -0
- data/lib/docscribe/cli/run.rb +415 -0
- data/lib/docscribe/cli.rb +31 -0
- data/lib/docscribe/config/defaults.rb +71 -0
- data/lib/docscribe/config/emit.rb +142 -0
- data/lib/docscribe/config/filtering.rb +160 -0
- data/lib/docscribe/config/loader.rb +59 -0
- data/lib/docscribe/config/rbs.rb +51 -0
- data/lib/docscribe/config/sorbet.rb +87 -0
- data/lib/docscribe/config/sorting.rb +23 -0
- data/lib/docscribe/config/template.rb +184 -0
- data/lib/docscribe/config/utils.rb +102 -0
- data/lib/docscribe/config.rb +20 -230
- data/lib/docscribe/infer/ast_walk.rb +28 -0
- data/lib/docscribe/infer/constants.rb +11 -0
- data/lib/docscribe/infer/literals.rb +55 -0
- data/lib/docscribe/infer/names.rb +43 -0
- data/lib/docscribe/infer/params.rb +62 -0
- data/lib/docscribe/infer/raises.rb +68 -0
- data/lib/docscribe/infer/returns.rb +171 -0
- data/lib/docscribe/infer.rb +104 -258
- data/lib/docscribe/inline_rewriter/collector.rb +845 -0
- data/lib/docscribe/inline_rewriter/doc_block.rb +383 -0
- data/lib/docscribe/inline_rewriter/doc_builder.rb +607 -0
- data/lib/docscribe/inline_rewriter/source_helpers.rb +228 -0
- data/lib/docscribe/inline_rewriter/tag_sorter.rb +244 -0
- data/lib/docscribe/inline_rewriter.rb +599 -428
- data/lib/docscribe/parsing.rb +55 -44
- data/lib/docscribe/types/provider_chain.rb +37 -0
- data/lib/docscribe/types/rbs/provider.rb +213 -0
- data/lib/docscribe/types/rbs/type_formatter.rb +132 -0
- data/lib/docscribe/types/signature.rb +65 -0
- data/lib/docscribe/types/sorbet/base_provider.rb +217 -0
- data/lib/docscribe/types/sorbet/rbi_provider.rb +35 -0
- data/lib/docscribe/types/sorbet/source_provider.rb +25 -0
- data/lib/docscribe/version.rb +1 -1
- metadata +37 -3
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
class Config
|
|
5
|
+
# Decide whether a file path should be processed based on `filter.files`.
|
|
6
|
+
#
|
|
7
|
+
# File paths are matched relative to the current working directory when possible.
|
|
8
|
+
# Exclude rules win. If no include rules are configured, files are included by default.
|
|
9
|
+
#
|
|
10
|
+
# @param [String] path file path to test
|
|
11
|
+
# @raise [StandardError]
|
|
12
|
+
# @return [Boolean]
|
|
13
|
+
def process_file?(path)
|
|
14
|
+
files = raw.dig('filter', 'files') || {}
|
|
15
|
+
include_patterns = normalize_file_patterns(files['include'])
|
|
16
|
+
exclude_patterns = normalize_file_patterns(files['exclude'])
|
|
17
|
+
|
|
18
|
+
rel = begin
|
|
19
|
+
Pathname.new(path).relative_path_from(Pathname.pwd).to_s
|
|
20
|
+
rescue StandardError
|
|
21
|
+
path
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
return false if file_matches_any?(exclude_patterns, rel)
|
|
25
|
+
return true if include_patterns.empty?
|
|
26
|
+
|
|
27
|
+
file_matches_any?(include_patterns, rel)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Decide whether a method should be processed based on configured method filters.
|
|
31
|
+
#
|
|
32
|
+
# Method IDs are normalized as:
|
|
33
|
+
# - instance method => `MyModule::MyClass#foo`
|
|
34
|
+
# - class method => `MyModule::MyClass.foo`
|
|
35
|
+
#
|
|
36
|
+
# Exclude rules win. If no include rules are configured, methods are included by default
|
|
37
|
+
# subject to scope and visibility allow-lists.
|
|
38
|
+
#
|
|
39
|
+
# @param [String] container enclosing class/module name
|
|
40
|
+
# @param [Symbol] scope :instance or :class
|
|
41
|
+
# @param [Symbol] visibility :public, :protected, or :private
|
|
42
|
+
# @param [String, Symbol] name method name
|
|
43
|
+
# @return [Boolean]
|
|
44
|
+
def process_method?(container:, scope:, visibility:, name:)
|
|
45
|
+
return false unless filter_scopes.include?(scope.to_s)
|
|
46
|
+
return false unless filter_visibilities.include?(visibility.to_s)
|
|
47
|
+
|
|
48
|
+
method_id = "#{container}#{scope == :instance ? '#' : '.'}#{name}"
|
|
49
|
+
|
|
50
|
+
return false if matches_any?(filter_exclude_patterns, method_id)
|
|
51
|
+
|
|
52
|
+
inc = filter_include_patterns
|
|
53
|
+
return true if inc.empty?
|
|
54
|
+
|
|
55
|
+
matches_any?(inc, method_id)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# Normalize file filter patterns:
|
|
61
|
+
# - compact nils
|
|
62
|
+
# - stringify
|
|
63
|
+
# - remove empties
|
|
64
|
+
# - expand shorthand directory forms
|
|
65
|
+
#
|
|
66
|
+
# @private
|
|
67
|
+
# @param [Array<String>, nil] list raw pattern list
|
|
68
|
+
# @return [Array<String>]
|
|
69
|
+
def normalize_file_patterns(list)
|
|
70
|
+
Array(list).compact.map(&:to_s).reject(&:empty?).flat_map { |pat| expand_directory_shorthand(pat) }.uniq
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Expand a directory-like pattern into a recursive glob when appropriate.
|
|
74
|
+
#
|
|
75
|
+
# Examples:
|
|
76
|
+
# - `"spec/"` => `"spec/**/*"`
|
|
77
|
+
# - `"spec"` => `"spec/**/*"` if `spec` exists as a directory
|
|
78
|
+
#
|
|
79
|
+
# @private
|
|
80
|
+
# @param [String] pattern
|
|
81
|
+
# @return [Array<String>]
|
|
82
|
+
def expand_directory_shorthand(pattern)
|
|
83
|
+
pat = pattern.dup
|
|
84
|
+
|
|
85
|
+
if pat.end_with?('/')
|
|
86
|
+
["#{pat}**/*"]
|
|
87
|
+
elsif !pat.match?(/[*?\[]|{/) && File.directory?(pat)
|
|
88
|
+
["#{pat}/**/*"]
|
|
89
|
+
else
|
|
90
|
+
[pat]
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Check whether a file path matches any configured file pattern.
|
|
95
|
+
#
|
|
96
|
+
# @private
|
|
97
|
+
# @param [Array<String>] patterns
|
|
98
|
+
# @param [String] path
|
|
99
|
+
# @return [Boolean]
|
|
100
|
+
def file_matches_any?(patterns, path)
|
|
101
|
+
patterns.any? { |pat| file_match_pattern?(pat, path) }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Match a file path against a single configured file filter.
|
|
105
|
+
#
|
|
106
|
+
# Supports:
|
|
107
|
+
# - `/regex/`
|
|
108
|
+
# - globs
|
|
109
|
+
# - recursive glob shorthand normalization
|
|
110
|
+
#
|
|
111
|
+
# @private
|
|
112
|
+
# @param [String] pattern
|
|
113
|
+
# @param [String] path
|
|
114
|
+
# @return [Boolean]
|
|
115
|
+
def file_match_pattern?(pattern, path)
|
|
116
|
+
if pattern.start_with?('/') && pattern.end_with?('/') && pattern.length >= 2
|
|
117
|
+
return Regexp.new(pattern[1..-2]).match?(path)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
patterns_to_try = [pattern]
|
|
121
|
+
patterns_to_try << pattern.gsub('/**/', '/') if pattern.include?('/**/')
|
|
122
|
+
|
|
123
|
+
patterns_to_try.any? do |pat|
|
|
124
|
+
File.fnmatch?(pat, path, File::FNM_EXTGLOB | File::FNM_PATHNAME)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Allowed method scopes from config/defaults.
|
|
129
|
+
#
|
|
130
|
+
# @private
|
|
131
|
+
# @return [Array<String>]
|
|
132
|
+
def filter_scopes
|
|
133
|
+
Array(raw.dig('filter', 'scopes') || DEFAULT.dig('filter', 'scopes')).map(&:to_s)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Allowed method visibilities from config/defaults.
|
|
137
|
+
#
|
|
138
|
+
# @private
|
|
139
|
+
# @return [Array<String>]
|
|
140
|
+
def filter_visibilities
|
|
141
|
+
Array(raw.dig('filter', 'visibilities') || DEFAULT.dig('filter', 'visibilities')).map(&:to_s)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Exclude method filter patterns.
|
|
145
|
+
#
|
|
146
|
+
# @private
|
|
147
|
+
# @return [Array<String>]
|
|
148
|
+
def filter_exclude_patterns
|
|
149
|
+
Array(raw.dig('filter', 'exclude') || DEFAULT.dig('filter', 'exclude')).map(&:to_s).reject(&:empty?)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Include method filter patterns.
|
|
153
|
+
#
|
|
154
|
+
# @private
|
|
155
|
+
# @return [Array<String>]
|
|
156
|
+
def filter_include_patterns
|
|
157
|
+
Array(raw.dig('filter', 'include') || DEFAULT.dig('filter', 'include')).map(&:to_s).reject(&:empty?)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
class Config
|
|
5
|
+
# Load Docscribe configuration from YAML.
|
|
6
|
+
#
|
|
7
|
+
# Resolution order:
|
|
8
|
+
# - explicit `path`, if it exists
|
|
9
|
+
# - `docscribe.yml` in the current directory, if present
|
|
10
|
+
# - otherwise defaults only
|
|
11
|
+
#
|
|
12
|
+
# @param [String, nil] path optional config path
|
|
13
|
+
# @return [Docscribe::Config]
|
|
14
|
+
def self.load(path = nil)
|
|
15
|
+
raw = {}
|
|
16
|
+
if path && File.file?(path)
|
|
17
|
+
raw = safe_load_file_compat(path)
|
|
18
|
+
elsif File.file?('docscribe.yml')
|
|
19
|
+
raw = safe_load_file_compat('docscribe.yml')
|
|
20
|
+
end
|
|
21
|
+
new(raw)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Safely load YAML from a file across Ruby/Psych versions.
|
|
25
|
+
#
|
|
26
|
+
# Uses `YAML.safe_load_file` when available, otherwise falls back to reading the file
|
|
27
|
+
# and calling {safe_load_compat}.
|
|
28
|
+
#
|
|
29
|
+
# @param [String] path file path
|
|
30
|
+
# @return [Hash]
|
|
31
|
+
def self.safe_load_file_compat(path)
|
|
32
|
+
if YAML.respond_to?(:safe_load_file)
|
|
33
|
+
YAML.safe_load_file(path, permitted_classes: [], permitted_symbols: [], aliases: true) || {}
|
|
34
|
+
else
|
|
35
|
+
yaml = File.open(path, 'r:bom|utf-8', &:read)
|
|
36
|
+
safe_load_compat(yaml, filename: path) || {}
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Safely load YAML from a string across Psych API versions.
|
|
41
|
+
#
|
|
42
|
+
# @param [String] yaml YAML document
|
|
43
|
+
# @param [String, nil] filename optional filename for diagnostics
|
|
44
|
+
# @raise [ArgumentError]
|
|
45
|
+
# @return [Hash]
|
|
46
|
+
def self.safe_load_compat(yaml, filename: nil)
|
|
47
|
+
Psych.safe_load(
|
|
48
|
+
yaml,
|
|
49
|
+
permitted_classes: [],
|
|
50
|
+
permitted_symbols: [],
|
|
51
|
+
aliases: true,
|
|
52
|
+
filename: filename
|
|
53
|
+
)
|
|
54
|
+
rescue ArgumentError
|
|
55
|
+
# Older Psych signature uses positional args
|
|
56
|
+
Psych.safe_load(yaml, [], [], true, filename)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
class Config
|
|
5
|
+
# Return a memoized RBS provider if RBS integration is enabled and available.
|
|
6
|
+
#
|
|
7
|
+
# If RBS cannot be loaded, this returns nil and Docscribe falls back to
|
|
8
|
+
# inference.
|
|
9
|
+
#
|
|
10
|
+
# @raise [LoadError]
|
|
11
|
+
# @return [Docscribe::Types::RBS::Provider, nil]
|
|
12
|
+
def rbs_provider
|
|
13
|
+
return nil unless rbs_enabled?
|
|
14
|
+
|
|
15
|
+
@rbs_provider ||= begin
|
|
16
|
+
require 'docscribe/types/rbs/provider'
|
|
17
|
+
Docscribe::Types::RBS::Provider.new(
|
|
18
|
+
sig_dirs: rbs_sig_dirs,
|
|
19
|
+
collapse_generics: rbs_collapse_generics?
|
|
20
|
+
)
|
|
21
|
+
rescue LoadError
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Whether RBS integration is enabled.
|
|
27
|
+
#
|
|
28
|
+
# @return [Boolean]
|
|
29
|
+
def rbs_enabled?
|
|
30
|
+
fetch_bool(%w[rbs enabled], false)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Signature directories used by the RBS provider.
|
|
34
|
+
#
|
|
35
|
+
# @return [Array<String>]
|
|
36
|
+
def rbs_sig_dirs
|
|
37
|
+
Array(raw.dig('rbs', 'sig_dirs') || DEFAULT.dig('rbs', 'sig_dirs')).map(&:to_s)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Whether generic RBS types should be collapsed to simpler container names.
|
|
41
|
+
#
|
|
42
|
+
# Examples:
|
|
43
|
+
# - `Hash<Symbol, String>` => `Hash`
|
|
44
|
+
# - `Array<Integer>` => `Array`
|
|
45
|
+
#
|
|
46
|
+
# @return [Boolean]
|
|
47
|
+
def rbs_collapse_generics?
|
|
48
|
+
fetch_bool(%w[rbs collapse_generics], false)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
class Config
|
|
5
|
+
# Build the effective external signature provider chain for a given source.
|
|
6
|
+
#
|
|
7
|
+
# Provider precedence is:
|
|
8
|
+
# 1. inline Sorbet signatures from the current source
|
|
9
|
+
# 2. Sorbet RBI files
|
|
10
|
+
# 3. RBS files
|
|
11
|
+
#
|
|
12
|
+
# Returns nil when no external type provider is enabled or available.
|
|
13
|
+
#
|
|
14
|
+
# @param [String] source Ruby source being rewritten
|
|
15
|
+
# @param [String] file source name for diagnostics
|
|
16
|
+
# @raise [LoadError]
|
|
17
|
+
# @return [Docscribe::Types::ProviderChain, nil]
|
|
18
|
+
def signature_provider_for(source:, file:)
|
|
19
|
+
providers = []
|
|
20
|
+
|
|
21
|
+
if sorbet_enabled?
|
|
22
|
+
begin
|
|
23
|
+
require 'docscribe/types/sorbet/source_provider'
|
|
24
|
+
providers << Docscribe::Types::Sorbet::SourceProvider.new(
|
|
25
|
+
source: source,
|
|
26
|
+
file: file,
|
|
27
|
+
collapse_generics: sorbet_collapse_generics?
|
|
28
|
+
)
|
|
29
|
+
rescue LoadError
|
|
30
|
+
# Sorbet support is optional; fall back quietly.
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
providers << sorbet_rbi_provider
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
providers << rbs_provider if rbs_enabled?
|
|
37
|
+
|
|
38
|
+
providers = providers.compact
|
|
39
|
+
return nil if providers.empty?
|
|
40
|
+
|
|
41
|
+
require 'docscribe/types/provider_chain'
|
|
42
|
+
Docscribe::Types::ProviderChain.new(*providers)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Return a memoized Sorbet RBI provider if Sorbet integration is enabled.
|
|
46
|
+
#
|
|
47
|
+
# @raise [LoadError]
|
|
48
|
+
# @return [Docscribe::Types::Sorbet::RBIProvider, nil]
|
|
49
|
+
def sorbet_rbi_provider
|
|
50
|
+
return nil unless sorbet_enabled?
|
|
51
|
+
|
|
52
|
+
@sorbet_rbi_provider ||= begin
|
|
53
|
+
require 'docscribe/types/sorbet/rbi_provider'
|
|
54
|
+
Docscribe::Types::Sorbet::RBIProvider.new(
|
|
55
|
+
rbi_dirs: sorbet_rbi_dirs,
|
|
56
|
+
collapse_generics: sorbet_collapse_generics?
|
|
57
|
+
)
|
|
58
|
+
rescue LoadError
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Whether Sorbet support is enabled in config.
|
|
64
|
+
#
|
|
65
|
+
# @return [Boolean]
|
|
66
|
+
def sorbet_enabled?
|
|
67
|
+
fetch_bool(%w[sorbet enabled], false)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# RBI directories searched by the Sorbet provider.
|
|
71
|
+
#
|
|
72
|
+
# @return [Array<String>]
|
|
73
|
+
def sorbet_rbi_dirs
|
|
74
|
+
Array(raw.dig('sorbet', 'rbi_dirs') || DEFAULT.dig('sorbet', 'rbi_dirs')).map(&:to_s)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Whether generic Sorbet/RBI container types should be simplified.
|
|
78
|
+
#
|
|
79
|
+
# Falls back to the RBS `collapse_generics` setting when Sorbet-specific
|
|
80
|
+
# config is not present.
|
|
81
|
+
#
|
|
82
|
+
# @return [Boolean]
|
|
83
|
+
def sorbet_collapse_generics?
|
|
84
|
+
fetch_bool(%w[sorbet collapse_generics], rbs_collapse_generics?)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
class Config
|
|
5
|
+
# Whether sortable tag normalization is enabled for doc-like blocks.
|
|
6
|
+
#
|
|
7
|
+
# @return [Boolean]
|
|
8
|
+
def sort_tags?
|
|
9
|
+
raw.dig('doc', 'sort_tags') != false
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Configured sortable tag order.
|
|
13
|
+
#
|
|
14
|
+
# Tags are normalized without a leading `@`.
|
|
15
|
+
#
|
|
16
|
+
# @return [Array<String>]
|
|
17
|
+
def tag_order
|
|
18
|
+
Array(raw.dig('doc', 'tag_order') || DEFAULT.dig('doc', 'tag_order')).map do |t|
|
|
19
|
+
t.to_s.sub(/\A@/, '')
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
class Config
|
|
5
|
+
# Return the default YAML template used by `docscribe init`.
|
|
6
|
+
#
|
|
7
|
+
# The template documents the most common CLI workflows and all supported
|
|
8
|
+
# configuration sections with comments.
|
|
9
|
+
# @see Docscribe::Config::DEFAULT
|
|
10
|
+
#
|
|
11
|
+
# @return [String]
|
|
12
|
+
def self.default_yaml
|
|
13
|
+
<<~YAML
|
|
14
|
+
---
|
|
15
|
+
# Docscribe configuration file
|
|
16
|
+
#
|
|
17
|
+
# Inspect what safe doc updates would be applied:
|
|
18
|
+
# bundle exec docscribe lib
|
|
19
|
+
#
|
|
20
|
+
# Apply safe doc updates:
|
|
21
|
+
# bundle exec docscribe -a lib
|
|
22
|
+
#
|
|
23
|
+
# Apply aggressive doc updates (rebuild existing doc blocks):
|
|
24
|
+
# bundle exec docscribe -A lib
|
|
25
|
+
#
|
|
26
|
+
|
|
27
|
+
emit:
|
|
28
|
+
# Emit the header line:
|
|
29
|
+
#
|
|
30
|
+
# +MyClass#my_method+ -> ReturnType
|
|
31
|
+
header: false
|
|
32
|
+
|
|
33
|
+
# Whether to include the default placeholder line:
|
|
34
|
+
# # Method documentation.
|
|
35
|
+
include_default_message: true
|
|
36
|
+
|
|
37
|
+
# Whether to append placeholder text to generated @param tags:
|
|
38
|
+
# # @param [String] name Param documentation.
|
|
39
|
+
include_param_documentation: true
|
|
40
|
+
|
|
41
|
+
# Emit @param tags.
|
|
42
|
+
param_tags: true
|
|
43
|
+
|
|
44
|
+
# Emit @return tag (can be overridden per scope/visibility under methods:).
|
|
45
|
+
return_tag: true
|
|
46
|
+
|
|
47
|
+
# Emit @private / @protected tags based on Ruby visibility context.
|
|
48
|
+
visibility_tags: true
|
|
49
|
+
|
|
50
|
+
# Emit @raise tags inferred from rescue clauses / raise/fail calls.
|
|
51
|
+
raise_tags: true
|
|
52
|
+
|
|
53
|
+
# Emit conditional rescue return tags:
|
|
54
|
+
#
|
|
55
|
+
# @return [String] if FooError, BarError
|
|
56
|
+
rescue_conditional_returns: true
|
|
57
|
+
|
|
58
|
+
# Generate @!attribute docs for attr_reader/attr_writer/attr_accessor.
|
|
59
|
+
attributes: false
|
|
60
|
+
|
|
61
|
+
doc:
|
|
62
|
+
# Default text inserted into each generated doc block.
|
|
63
|
+
default_message: "Method documentation."
|
|
64
|
+
|
|
65
|
+
# Default text appended to generated @param tags.
|
|
66
|
+
param_documentation: "Param documentation."
|
|
67
|
+
|
|
68
|
+
# Style for generated @param tags:
|
|
69
|
+
# - type_name => @param [Type] name
|
|
70
|
+
# - name_type => @param name [Type]
|
|
71
|
+
param_tag_style: "type_name"
|
|
72
|
+
|
|
73
|
+
# Sort generated / merged tags in safe mode when possible.
|
|
74
|
+
sort_tags: true
|
|
75
|
+
|
|
76
|
+
# Tag order used when sorting contiguous tag runs.
|
|
77
|
+
tag_order: ["todo", "note", "api", "private", "protected", "param", "option", "yieldparam", "raise", "return"]
|
|
78
|
+
|
|
79
|
+
methods:
|
|
80
|
+
# Per-scope / per-visibility overrides.
|
|
81
|
+
#
|
|
82
|
+
# Example:
|
|
83
|
+
# methods:
|
|
84
|
+
# instance:
|
|
85
|
+
# public:
|
|
86
|
+
# default_message: "Public API."
|
|
87
|
+
# return_tag: true
|
|
88
|
+
instance:
|
|
89
|
+
public: {}
|
|
90
|
+
protected: {}
|
|
91
|
+
private: {}
|
|
92
|
+
|
|
93
|
+
class:
|
|
94
|
+
public: {}
|
|
95
|
+
protected: {}
|
|
96
|
+
private: {}
|
|
97
|
+
|
|
98
|
+
inference:
|
|
99
|
+
# Type used when inference is uncertain.
|
|
100
|
+
fallback_type: "Object"
|
|
101
|
+
|
|
102
|
+
# Whether nil unions become optional types (for example String | nil => String?).
|
|
103
|
+
nil_as_optional: true
|
|
104
|
+
|
|
105
|
+
# Special-case: treat keyword arg named options/options: as a Hash.
|
|
106
|
+
treat_options_keyword_as_hash: true
|
|
107
|
+
|
|
108
|
+
filter:
|
|
109
|
+
# Filter which methods Docscribe touches.
|
|
110
|
+
#
|
|
111
|
+
# Method id format:
|
|
112
|
+
# instance: "MyModule::MyClass#instance_method"
|
|
113
|
+
# class: "MyModule::MyClass.class_method"
|
|
114
|
+
#
|
|
115
|
+
# Patterns:
|
|
116
|
+
# - glob: "*#initialize", "MyApp::*#*"
|
|
117
|
+
# - regex: "/^MyApp::.*#(foo|bar)$/"
|
|
118
|
+
#
|
|
119
|
+
# Semantics:
|
|
120
|
+
# - scopes / visibilities act as allow-lists
|
|
121
|
+
# - exclude wins
|
|
122
|
+
# - if include is empty => include everything (subject to allow-lists)
|
|
123
|
+
visibilities: ["public", "protected", "private"]
|
|
124
|
+
scopes: ["instance", "class"]
|
|
125
|
+
include: []
|
|
126
|
+
exclude: []
|
|
127
|
+
|
|
128
|
+
files:
|
|
129
|
+
# Filter which files Docscribe processes (paths are matched relative
|
|
130
|
+
# to the project root).
|
|
131
|
+
#
|
|
132
|
+
# Tips:
|
|
133
|
+
# - Use directory shorthand to exclude a whole directory:
|
|
134
|
+
# exclude: ["spec"]
|
|
135
|
+
# - Or use globs:
|
|
136
|
+
# exclude: ["spec/**/*.rb", "vendor/**/*.rb"]
|
|
137
|
+
include: []
|
|
138
|
+
exclude: ["spec"]
|
|
139
|
+
|
|
140
|
+
rbs:
|
|
141
|
+
# Optional: use RBS signatures to improve @param / @return types.
|
|
142
|
+
#
|
|
143
|
+
# CLI equivalent:
|
|
144
|
+
# bundle exec docscribe -a --rbs --sig-dir sig lib
|
|
145
|
+
#
|
|
146
|
+
# Under Bundler, you may need `gem "rbs"` in your Gemfile (or a
|
|
147
|
+
# Gemfile that includes it), otherwise `require "rbs"` may fail and
|
|
148
|
+
# Docscribe will fall back to inference.
|
|
149
|
+
enabled: false
|
|
150
|
+
|
|
151
|
+
# Signature directories (repeatable via --sig-dir).
|
|
152
|
+
sig_dirs: ["sig"]
|
|
153
|
+
|
|
154
|
+
# If true, simplify generic types:
|
|
155
|
+
# - Hash<Symbol, String> => Hash
|
|
156
|
+
# - Array<Integer> => Array
|
|
157
|
+
collapse_generics: false
|
|
158
|
+
|
|
159
|
+
sorbet:
|
|
160
|
+
# Optional: use Sorbet signatures from inline `sig` declarations and
|
|
161
|
+
# RBI files to improve @param / @return types.
|
|
162
|
+
#
|
|
163
|
+
# CLI equivalent:
|
|
164
|
+
# bundle exec docscribe -a --sorbet --rbi-dir sorbet/rbi lib
|
|
165
|
+
#
|
|
166
|
+
# Sorbet resolution order is:
|
|
167
|
+
# 1. inline `sig` in the current source file
|
|
168
|
+
# 2. RBI files
|
|
169
|
+
# 3. RBS
|
|
170
|
+
# 4. AST inference
|
|
171
|
+
enabled: false
|
|
172
|
+
|
|
173
|
+
# RBI directories scanned recursively for `.rbi` files
|
|
174
|
+
# (repeatable via --rbi-dir).
|
|
175
|
+
rbi_dirs: ["sorbet/rbi", "rbi"]
|
|
176
|
+
|
|
177
|
+
# If true, simplify generic types:
|
|
178
|
+
# - Hash<Symbol, String> => Hash
|
|
179
|
+
# - Array<Integer> => Array
|
|
180
|
+
collapse_generics: false
|
|
181
|
+
YAML
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
class Config
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
# Fetch a boolean method-level override for a given scope/visibility pair.
|
|
8
|
+
#
|
|
9
|
+
# @private
|
|
10
|
+
# @param [Symbol] scope :instance or :class
|
|
11
|
+
# @param [Symbol] vis :public, :protected, or :private
|
|
12
|
+
# @param [String] key override key
|
|
13
|
+
# @param [Boolean] default fallback value
|
|
14
|
+
# @return [Boolean]
|
|
15
|
+
def method_override_bool(scope, vis, key, default:)
|
|
16
|
+
node = raw.dig('methods', scope_to_key(scope), vis.to_s, key)
|
|
17
|
+
node.nil? ? default : !!node
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Fetch a string method-level override for a given scope/visibility pair.
|
|
21
|
+
#
|
|
22
|
+
# @private
|
|
23
|
+
# @param [Symbol] scope :instance or :class
|
|
24
|
+
# @param [Symbol] vis :public, :protected, or :private
|
|
25
|
+
# @param [String] key override key
|
|
26
|
+
# @param [String] default fallback value
|
|
27
|
+
# @return [String]
|
|
28
|
+
def method_override_str(scope, vis, key, default:)
|
|
29
|
+
node = raw.dig('methods', scope_to_key(scope), vis.to_s, key)
|
|
30
|
+
node.nil? ? default : node.to_s
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Fetch a boolean config value by nested path with a default fallback.
|
|
34
|
+
#
|
|
35
|
+
# @private
|
|
36
|
+
# @param [Array<String>] path nested config keys
|
|
37
|
+
# @param [Boolean] default fallback value
|
|
38
|
+
# @return [Boolean]
|
|
39
|
+
def fetch_bool(path, default)
|
|
40
|
+
node = raw
|
|
41
|
+
path.each { |k| node = node[k] if node }
|
|
42
|
+
node.nil? ? default : !!node
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Convert an internal scope symbol into the config key used under `methods`.
|
|
46
|
+
#
|
|
47
|
+
# @private
|
|
48
|
+
# @param [Symbol] scope
|
|
49
|
+
# @return [String]
|
|
50
|
+
def scope_to_key(scope)
|
|
51
|
+
scope == :class ? 'class' : 'instance'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Check whether any pattern matches the given text.
|
|
55
|
+
#
|
|
56
|
+
# @private
|
|
57
|
+
# @param [Array<String>] patterns
|
|
58
|
+
# @param [String] text
|
|
59
|
+
# @return [Boolean]
|
|
60
|
+
def matches_any?(patterns, text)
|
|
61
|
+
patterns.any? { |pat| match_pattern?(pat, text) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Match a method filter pattern against a method ID.
|
|
65
|
+
#
|
|
66
|
+
# Supports:
|
|
67
|
+
# - `/regex/`
|
|
68
|
+
# - shell-style glob patterns
|
|
69
|
+
#
|
|
70
|
+
# @private
|
|
71
|
+
# @param [String] pattern
|
|
72
|
+
# @param [String] text
|
|
73
|
+
# @return [Boolean]
|
|
74
|
+
def match_pattern?(pattern, text)
|
|
75
|
+
if pattern.start_with?('/') && pattern.end_with?('/') && pattern.length >= 2
|
|
76
|
+
Regexp.new(pattern[1..-2]).match?(text)
|
|
77
|
+
else
|
|
78
|
+
File.fnmatch?(pattern, text, File::FNM_EXTGLOB)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Deep-merge two hashes, preferring values from the second hash.
|
|
83
|
+
#
|
|
84
|
+
# Nested hashes are merged recursively; non-hash values are replaced.
|
|
85
|
+
#
|
|
86
|
+
# @private
|
|
87
|
+
# @param [Hash] hash1 base hash
|
|
88
|
+
# @param [Hash, nil] hash2 override hash
|
|
89
|
+
# @return [Hash]
|
|
90
|
+
def deep_merge(hash1, hash2)
|
|
91
|
+
return hash1 unless hash2
|
|
92
|
+
|
|
93
|
+
hash1.merge(hash2) do |_, v1, v2|
|
|
94
|
+
if v1.is_a?(Hash) && v2.is_a?(Hash)
|
|
95
|
+
deep_merge(v1, v2)
|
|
96
|
+
else
|
|
97
|
+
v2
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|