# typed: strict # frozen_string_literal: true require "ruby_lsp/internal" require "objspace" module RubyLsp # This rake task checks that all requests or addons are fully documented. Add the rake task to your Rakefile and # specify the absolute path for all files that must be required in order to discover all requests and their related # GIFs # # # Rakefile # request_files = FileList.new("#{__dir__}/lib/ruby_lsp/requests/*.rb") do |fl| # fl.exclude(/base_request\.rb/) # end # gif_files = FileList.new("#{__dir__}/**/*.gif") # RubyLsp::CheckDocs.new(request_files, gif_files) # # Run with bundle exec rake ruby_lsp:check_docs class CheckDocs < Rake::TaskLib extend T::Sig sig { params(require_files: Rake::FileList, gif_files: Rake::FileList).void } def initialize(require_files, gif_files) super() @name = T.let("ruby_lsp:check_docs", String) @file_list = require_files @gif_list = gif_files define_task end private sig { void } def define_task desc("Checks if all Ruby LSP requests are documented") task(@name) { run_task } end sig { params(request_path: String).returns(T::Boolean) } def gif_exists?(request_path) request_gif = request_path.gsub(".rb", ".gif").split("/").last @gif_list.any? { |gif_path| gif_path.end_with?(request_gif) } end sig { void } def run_task # Require all files configured to make sure all requests are loaded @file_list.each { |f| require(f.delete_suffix(".rb")) } # Find all classes that inherit from BaseRequest, which are the ones we want to make sure are # documented features = ObjectSpace.each_object(Class).select do |k| klass = T.unsafe(k) klass < Requests::Request end missing_docs = T.let(Hash.new { |h, k| h[k] = [] }, T::Hash[String, T::Array[String]]) features.each do |klass| class_name = T.unsafe(klass).name file_path, line_number = Module.const_source_location(class_name) next unless file_path && line_number # Adjust the line number to start searching right above the class definition line_number -= 2 lines = File.readlines(file_path) docs = [] # Extract the documentation on top of the request constant while (line = lines[line_number]&.strip) && line.start_with?("#") docs.unshift(line) line_number -= 1 end documentation = docs.join("\n") if docs.empty? T.must(missing_docs[class_name]) << "No documentation found" elsif !%r{\(https://microsoft.github.io/language-server-protocol/specification#.*\)}.match?(documentation) T.must(missing_docs[class_name]) << <<~DOCS Missing specification link. Requests and addons should include a link to the LSP specification for the related feature. For example: [Inlay hint](https://microsoft.github.io/language-server-protocol/specification#textDocument_inlayHint) DOCS elsif !documentation.include?("# Example") T.must(missing_docs[class_name]) << <<~DOCS Missing example. Requests and addons should include a code example that explains what the feature does. # # Example # ```ruby # class Foo # <- information is shown here # end # ``` DOCS elsif !/\[.* demo\]\(.*\.gif\)/.match?(documentation) T.must(missing_docs[class_name]) << <<~DOCS Missing demonstration GIF. Each request and addon must be documented with a GIF that shows the feature working. For example: # [Inlay hint demo](../../inlay_hint.gif) DOCS elsif !gif_exists?(file_path) T.must(missing_docs[class_name]) << <<~DOCS The GIF for the request documentation does not exist. Make sure to add it, with the same naming as the request. For example: # lib/ruby_lsp/requests/code_lens.rb # foo/bar/code_lens.gif DOCS end end if missing_docs.any? $stderr.puts(<<~WARN) The following requests are missing documentation: #{missing_docs.map { |k, v| "#{k}\n\n#{v.join("\n")}" }.join("\n\n")} WARN abort end puts "All requests are documented!" end end end