From fc61cf5bc894bb6750de69c5e618f199ff1b59e3 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 30 Jul 2026 19:00:43 +0100 Subject: [PATCH] Support per-gem documentation exclusions --- README.md | 9 ++++++ lib/tapioca/cli.rb | 6 +++- lib/tapioca/commands/abstract_gem.rb | 5 +++- lib/tapioca/commands/configure.rb | 4 +++ lib/tapioca/helpers/config_helper.rb | 18 +++++++++++- spec/tapioca/cli/config_spec.rb | 43 +++++++++++++++++++++++++++- spec/tapioca/cli/gem_spec.rb | 42 +++++++++++++++++++++++++++ 7 files changed, 123 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dd107435c..ba7cdf597 100644 --- a/README.md +++ b/README.md @@ -974,6 +974,15 @@ gem: doc: true ``` +To generate documentation for all gems except specific ones, set `doc.exclude` to the gem names. This form enables documentation. The excluded gems still get RBI files, but comments from their source code are not copied into those files: + +```yaml +gem: + doc: + exclude: + - irb +``` + Additionally, if you always want to exclude the `AASM` and `ActiveRecordFixtures` DSL compilers in your DSL RBI generation runs, your config file would then look like this: ```yaml diff --git a/lib/tapioca/cli.rb b/lib/tapioca/cli.rb index a46468f27..f2eff3148 100644 --- a/lib/tapioca/cli.rb +++ b/lib/tapioca/cli.rb @@ -302,6 +302,9 @@ def gem(*gems) raise MalformattedArgumentError, "Option '--verify' must be provided without any other arguments" if verify end + doc = options[:doc] + doc_exclude = doc.is_a?(Hash) ? doc.fetch("exclude", []) : [] + command_args = { gem_names: all ? [] : gems, exclude: options[:exclude], @@ -311,7 +314,8 @@ def gem(*gems) typed_overrides: options[:typed_overrides], outpath: Pathname.new(options[:outdir]), file_header: options[:file_header], - include_doc: options[:doc], + include_doc: doc != false, + doc_exclude: doc_exclude, include_loc: options[:loc], include_exported_rbis: options[:exported_gem_rbis], number_of_workers: options[:workers], diff --git a/lib/tapioca/commands/abstract_gem.rb b/lib/tapioca/commands/abstract_gem.rb index 83c52e355..786ee40e6 100644 --- a/lib/tapioca/commands/abstract_gem.rb +++ b/lib/tapioca/commands/abstract_gem.rb @@ -20,6 +20,7 @@ class AbstractGem < Command #| include_doc: bool, #| include_loc: bool, #| include_exported_rbis: bool, + #| ?doc_exclude: Array[String], #| ?number_of_workers: Integer?, #| ?auto_strictness: bool, #| ?dsl_dir: String, @@ -40,6 +41,7 @@ def initialize( include_doc:, include_loc:, include_exported_rbis:, + doc_exclude: [], number_of_workers: nil, auto_strictness: true, dsl_dir: DEFAULT_DSL_DIR, @@ -69,6 +71,7 @@ def initialize( @existing_rbis = nil #: Hash[String, String]? @expected_rbis = nil #: Hash[String, String]? @include_doc = include_doc #: bool + @doc_exclude = doc_exclude #: Array[String] @include_loc = include_loc #: bool @include_exported_rbis = include_exported_rbis @halt_upon_load_error = halt_upon_load_error @@ -92,7 +95,7 @@ def compile_gem_rbi(gem) rbi.root = Runtime.with_disabled_exits do Tapioca::Gem::Pipeline.new( gem, - include_doc: @include_doc, + include_doc: @include_doc && !@doc_exclude.include?(gem.name), include_loc: @include_loc, error_handler: ->(error) { say_error(error, :bold, :red) diff --git a/lib/tapioca/commands/configure.rb b/lib/tapioca/commands/configure.rb index 7e1173a12..25642aaab 100644 --- a/lib/tapioca/commands/configure.rb +++ b/lib/tapioca/commands/configure.rb @@ -51,6 +51,10 @@ def create_tapioca_config # exclude: # - gem_name # doc: true + # Or exclude documentation for specific gems: + # doc: + # exclude: + # - gem_name # workers: 5 dsl: # Add your `dsl` command parameters here: diff --git a/lib/tapioca/helpers/config_helper.rb b/lib/tapioca/helpers/config_helper.rb index e96c4c59c..1a17bbde9 100644 --- a/lib/tapioca/helpers/config_helper.rb +++ b/lib/tapioca/helpers/config_helper.rb @@ -99,6 +99,21 @@ def validate_config_options(command_options, config_key, config_options) error_msg = "unknown option `#{config_option_key}` for key `#{config_key}`" next build_error(error_msg) unless command_option + gem_doc_option = config_key.to_s == "gem" && config_option_key.to_s == "doc" + if gem_doc_option && config_option_value.is_a?(Hash) + unknown_key = config_option_value.keys.find { |key| key != "exclude" } + if unknown_key + next build_error("unknown option `#{unknown_key}` for option `doc` for key `#{config_key}`") + end + + excluded_gems = config_option_value["exclude"] + next if excluded_gems.is_a?(Array) && excluded_gems.all? { |gem| gem.is_a?(String) } + + error_msg = "invalid value for option `doc.exclude` for key `#{config_key}` - expected " \ + "`Array[String]` but found `#{excluded_gems}`" + next build_error(error_msg) + end + config_option_value_type = case config_option_value when FalseClass, TrueClass :boolean @@ -114,8 +129,9 @@ def validate_config_options(command_options, config_key, config_options) :object end + expected_type = gem_doc_option ? "Boolean or Hash" : command_option.type.capitalize error_msg = "invalid value for option `#{config_option_key}` for key `#{config_key}` - expected " \ - "`#{command_option.type.capitalize}` but found #{config_option_value_type.capitalize}" + "`#{expected_type}` but found #{config_option_value_type.capitalize}" next build_error(error_msg) unless config_option_value_type == command_option.type case config_option_value_type diff --git a/spec/tapioca/cli/config_spec.rb b/spec/tapioca/cli/config_spec.rb index 4dda248c4..69e61a3e4 100644 --- a/spec/tapioca/cli/config_spec.rb +++ b/spec/tapioca/cli/config_spec.rb @@ -86,7 +86,7 @@ class ConfigTest < SpecWithProject Configuration file sorbet/tapioca/config.yml has the following errors: - - invalid value for option doc for key gem - expected Boolean but found String + - invalid value for option doc for key gem - expected Boolean or Hash but found String - invalid value for option typed_overrides for key gem - expected Hash but found Array - invalid value for option workers for key gem - expected Numeric but found String - invalid value for option exclude for key dsl - expected Array but found Boolean @@ -96,6 +96,47 @@ class ConfigTest < SpecWithProject refute_success_status(result) end + it "validates invalid gem documentation exclusions" do + @project.write!("sorbet/tapioca/config.yml", <<~YAML) + gem: + doc: + exclude: irb + YAML + + result = @project.tapioca("gem") + + assert_stderr_equals(<<~ERR, result) + + Configuration file sorbet/tapioca/config.yml has the following errors: + + - invalid value for option doc.exclude for key gem - expected Array[String] but found irb + ERR + + assert_empty_stdout(result) + refute_success_status(result) + end + + it "validates unknown gem documentation options" do + @project.write!("sorbet/tapioca/config.yml", <<~YAML) + gem: + doc: + only: + - irb + YAML + + result = @project.tapioca("gem") + + assert_stderr_equals(<<~ERR, result) + + Configuration file sorbet/tapioca/config.yml has the following errors: + + - unknown option only for option doc for key gem + ERR + + assert_empty_stdout(result) + refute_success_status(result) + end + it "validates invalid configuration option values inside arrays and hashes" do @project.write!("sorbet/tapioca/config.yml", <<~YAML) dsl: diff --git a/spec/tapioca/cli/gem_spec.rb b/spec/tapioca/cli/gem_spec.rb index 61484ad51..1187821b8 100644 --- a/spec/tapioca/cli/gem_spec.rb +++ b/spec/tapioca/cli/gem_spec.rb @@ -9,9 +9,11 @@ class GemSpec < SpecWithProject include Tapioca::Helpers::Test::Template FOO_RB = <<~RB + # Foo source documentation module Foo PI = 3.1415 + # Foo method documentation def self.foo(a = 1, b: 2, **opts) number = opts[:number] || 0 39 + a + b + number @@ -43,9 +45,11 @@ module Reopened; end RBI BAR_RB = <<~RB + # Bar source documentation module Bar PI = 3.1415 + # Bar method documentation def self.bar(a = 1, b: 2, **opts) number = opts[:number] || 0 39 + a + b + number @@ -270,6 +274,44 @@ def fizz; end assert_success_status(result) end + it "excludes source documentation for configured gems without excluding their RBIs" do + foo = mock_gem("foo", "0.0.1") do + write!("lib/foo.rb", FOO_RB) + end + + bar = mock_gem("bar", "0.3.0") do + write!("lib/bar.rb", BAR_RB) + end + + @project.require_mock_gem(foo) + @project.require_mock_gem(bar) + @project.bundle_install! + @project.write!("doc_config.yml", <<~YAML) + gem: + doc: + exclude: + - foo + YAML + + result = @project.bundle_exec( + "tapioca gem foo bar --workers=1 --no-loc --config doc_config.yml", + { "ENFORCE_TYPECHECKING" => "1" }, + ) + + foo_rbi = @project.read("sorbet/rbi/gems/foo@0.0.1.rbi") + bar_rbi = @project.read("sorbet/rbi/gems/bar@0.3.0.rbi") + + assert_includes(foo_rbi, "module Foo") + refute_includes(foo_rbi, "Foo source documentation") + refute_includes(foo_rbi, "Foo method documentation") + assert_includes(bar_rbi, "Bar source documentation") + assert_includes(bar_rbi, "Bar method documentation") + assert_empty_stderr(result) + assert_success_status(result) + ensure + @project.remove!("doc_config.yml") + end + it "must generate RBI for a default gem" do gem_name = "singleton" gem_top_level_constant = "module Singleton"