Closed jjb closed 1 month ago
in case helplful to someone finding this, here's what i'm trying in CircleCI
set +e # do not exit right away if a command fails https://circleci.com/docs/configuration-reference/#default-shell-options
yard doc --fail-on-warning --no-output &> yard_out
grep "\[warn\]" yard_out | grep -ivq undocumentable
if [ $? -eq 0 ]; then # grep found a warning that is not undocumentable? that's bad
echo "IMPORTANT: Do not try to fix 'Undocumentable' errors, only fix the others"
echo
cat yard_out
echo
echo "IMPORTANT: Do not try to fix 'Undocumentable' errors, only fix the others"
exit 1
fi
cat yard_out # all good! show output and exit success
is there any way to ignore or document these so that there are no warnings?
This is a bit of a loaded question. There is no way to retain the same syntax and not have YARD display warnings. By the same token, if you do not want warnings to fail your CI build, you should not use the built-in --fail-on-warning
option. YARD's fail-on-warning option is intentionally simplistic, since YARD's core codebase itself is not a documentation linter; the fail on warning option is a convenience to provide an error code if any warning text was printed. tl;dr, fail-on-warning does not allow for customization.
In general, the solution to providing documentable code is to reduce your use of dynamic calls such as the one you've listed. In almost all cases, there is a non-dynamic alternative to dynamic Ruby syntaxes. In the case of your specific example, you could use Rails.application.routes.url_helpers.YOUR_ROUTE
directly. Another Rails specific solution would be to wrap the include statement inside of a Concern:
module WithRoutes
extend ActiveSupport::Concern
included do
include Rails.application.routes.url_helpers
end
end
class MyController < ApplicationController
include WithRoutes
end
This will bypass top-level inclusion in the YARD parser.
If you do not want to change your syntax, the solution you've come up with is basically the simplest "recommended" path as far as implementing simple custom linting in CI. If you want something a little more robust, you can consider using YARD extensions, like overriding YARD::Logger#warn to do this type of parsing in Ruby, or via an extension to specific handlers. The latter would afford you a lot more flexibility about what type of code is allowed and when, for example:
# yard_extensions/ignore_rails_mixin.rb
module IgnoreRailsMixin
def process
super
rescue YARD::Parser::UndocumentableError => e
raise e unless statement.last.source.start_with?("Rails.")
end
end
YARD::Handlers::Ruby::MixinHandler.prepend(IgnoreRailsMixin)
You can use the above via yard -e yard_extensions/ignore_rails_mixin.rb
or by adding -e yard_extensions/ignore_rails_mixin.rb
to your .yardopts
.
Wow, thank you so much for these solutions!
Here's what I came up with, I couldn't figure out a way to get it into the top level process method, since it's done with metaprogramming
# https://github.com/lsegal/yard/issues/1542
module YardIgnoreUndocumentable
def process
super
rescue YARD::Parser::UndocumentableError => e
puts '😎'
end
end
YARD::Handlers::Ruby::MixinHandler.prepend(YardIgnoreUndocumentable)
YARD::Handlers::Ruby::AttributeHandler.prepend(YardIgnoreUndocumentable)
YARD::Handlers::Ruby::MethodHandler.prepend(YardIgnoreUndocumentable)
and then running this in CI
yard doc -e lib/yard_extensions/ignore_undocumentable.rb --fail-on-warning # do not add --no-output
I found this thread really useful. Here is a version that comes with a config file. Probably worth making it a plugin in the future.
Extension
# yard_extensions/ignore_warnings.rb
module IgnoreWarnings
def process
super
rescue YARD::Parser::UndocumentableError => e
raise e unless ::IgnoreWarnings::Registry.instance.ignore?(exception: e, statement: statement)
end
end
require_relative "ignore_warnings/registry"
IgnoreWarnings::Registry.initialize_instance(File.join(File.dirname(__FILE__), "../yard.yml"))
YARD::Handlers::Ruby::ClassHandler.prepend(IgnoreWarnings)
YARD::Handlers::Ruby::MixinHandler.prepend(IgnoreWarnings)
# yard_extensions/ignore_warnings/registry.rb
class IgnoreWarnings::Registry
attr_reader :rules
attr_accessor :debug
def initialize
@debug = false
@rules = []
end
def add_rule(hash = {})
if hash.nil? || hash.empty?
raise ArgumentError, "Invalid rule: #{hash}"
end
unknown_keys = hash.keys - %w[file line error_class source]
unless unknown_keys.empty?
raise ArgumentError, "Unknown rule keys #{unknown_keys.map(&:inspect).join(", ")} in #{hash}"
end
@rules << (hash.map { |k, v| [k.to_sym, v] }.to_h)
end
def ignore?(exception:, statement:)
file = statement.last.file
source = statement.last.source
error_class = exception.class.name
line = statement.last.line
@rules.each do |r|
next if r.key?(:error) && r[:error] != error_class
next if r.key?(:file) && r[:file] != file
next if r.key?(:line) && r[:line] != line
next if r.key?(:source) && r[:source] != source
if debug
puts "* [IgnoreWarnings] Rule Matched:\n rule: #{r}\n error_class: #{error_class.inspect}\n file: #{file}\n line: #{line}\n source: #{source.inspect}"
end
return true
end
if debug
puts "* [IgnoreWarnings] No rules matched:\n error_class: #{error_class.inspect}\n file: #{file}\n line: #{line}\n source: #{source.inspect}"
end
false
end
private
class << self
def new_from_config(path)
require "yaml"
new.tap do |registry|
config = YAML.safe_load_file(path)
rules = config.dig("yard", "ignore_warnings", "rules")
registry.debug = config.dig("yard", "ignore_warnings", "debug")
if rules
rules.each { |r| registry.add_rule(r) }
else
raise "No rules found in: #{config}"
end
end
end
def initialize_instance(config_path)
@instance = new_from_config(config_path)
end
def instance
@instance || raise("Did you forget to call IgnoreWarnings::Registry.initialize_instance?")
end
end
end
Configuration
# yard.yml
yard:
ignore_warnings:
# debug: true
rules:
- file: app/lib/route_helpers.rb
error_class: YARD::Parser::UndocumentableError
source: Rails.application.routes.url_helpers
- file: lib/core_ext/to_bool.rb
error_class: YARD::Parser::UndocumentableError
line: 21
Hope it helps someone else!
i want to run
yard doc --fail-on-warning --no-output
in CI and confirm successi have several instances of undocumentable code
one instance is this in a rails model, also the subject of this 12-year-old SO question which i just put a bounty on 😄
is there any way to ignore or document these so that there are no warnings?
i researched high and low before writing this, i hope i didn't miss something.
thanks for a great project!