├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .rubocop.yml ├── .standard.yml ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib ├── yard-rustdoc.rb └── yard-rustdoc │ ├── def_parser.rb │ ├── handlers.rb │ ├── parser.rb │ ├── statements.rb │ └── version.rb ├── test ├── integration_test.rb ├── samples │ └── example-ext │ │ ├── .cargo │ │ └── config.toml │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── Gemfile │ │ ├── Gemfile.lock │ │ ├── Rakefile │ │ ├── ext │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── extconf.rb │ │ └── src │ │ │ └── lib.rs │ │ ├── lib │ │ └── example.rb │ │ └── rustdoc │ │ ├── v33.json │ │ └── v39.json └── test_helper.rb └── yard-rustdoc.gemspec /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: push 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | ruby: ["3.1", "3.2", "3.3", "3.4"] 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Remove Gemfile.lock 14 | run: rm Gemfile.lock 15 | - name: Set up Ruby ${{ matrix.ruby }} 16 | uses: ruby/setup-ruby@v1 17 | with: 18 | ruby-version: ${{ matrix.ruby }} 19 | bundler-cache: true 20 | - name: Install dependencies 21 | run: bundle install 22 | - name: Run tests 23 | run: bundle exec rake 24 | 25 | lint: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v3 29 | - name: Remove Gemfile.lock 30 | run: rm Gemfile.lock 31 | - name: Set up Ruby 32 | uses: ruby/setup-ruby@v1 33 | with: 34 | ruby-version: "3.4" 35 | bundler-cache: true 36 | - name: Install dependencies 37 | run: bundle install 38 | - name: Lint 39 | run: bundle exec standardrb 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | **/tmp/ 9 | *.bundle 10 | *.so 11 | *.o 12 | *.a 13 | mkmf.log 14 | target/ 15 | 16 | .yardoc 17 | test/samples/**/doc 18 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: standard 2 | 3 | inherit_gem: 4 | standard: config/base.yml 5 | -------------------------------------------------------------------------------- /.standard.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "**/tmp/**/*" 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | 5 | ## v0.4.1 6 | 7 | - Adjust Rustdoc's json parsing for format v39. 8 | 9 | ## v0.4.0 10 | 11 | - Adjust Rustdoc's json parsing for format v26. 12 | See https://github.com/rust-lang/rust/pull/111427 for the breaking change. 13 | - Look at 2nd argument when checking for `rb_self` to infer the scope 14 | (class/instance) method (in addition to the 1st arg). 15 | This is because magnus can inject a `magnus::Ruby` as the 1st argument. 16 | - Ensure nested constants (`A::B`) show up in YARD's class list, 17 | regardless of which order they're in rustdoc's JSON. 18 | 19 | ## v0.3.2 20 | 21 | - Fix bad release in v0.3.1 22 | 23 | ## v0.3.1 24 | 25 | - Add `@module` tag to turn a struct into a module. 26 | 27 | ## v0.3.0 28 | 29 | - Remove the `@yard` annotation when sending to docstring to YARD. 30 | It's unecessary and makes it harder to use the `(see ...)` pattern. 31 | 32 | ## v0.2.0 33 | 34 | - Define instance methods when the function's first parameter is `rb_self` 35 | - Support documenting `Enum`s. 36 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gemspec 6 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | yard-rustdoc (0.4.1) 5 | syntax_tree (~> 6.0) 6 | yard (~> 0.9) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | ast (2.4.2) 12 | json (2.9.1) 13 | language_server-protocol (3.17.0.4) 14 | lint_roller (1.1.0) 15 | minitest (5.25.4) 16 | parallel (1.26.3) 17 | parser (3.3.7.0) 18 | ast (~> 2.4.1) 19 | racc 20 | prettier_print (1.2.1) 21 | racc (1.8.1) 22 | rainbow (3.1.1) 23 | rake (13.2.1) 24 | regexp_parser (2.10.0) 25 | rubocop (1.70.0) 26 | json (~> 2.3) 27 | language_server-protocol (>= 3.17.0) 28 | parallel (~> 1.10) 29 | parser (>= 3.3.0.2) 30 | rainbow (>= 2.2.2, < 4.0) 31 | regexp_parser (>= 2.9.3, < 3.0) 32 | rubocop-ast (>= 1.36.2, < 2.0) 33 | ruby-progressbar (~> 1.7) 34 | unicode-display_width (>= 2.4.0, < 4.0) 35 | rubocop-ast (1.38.0) 36 | parser (>= 3.3.1.0) 37 | rubocop-performance (1.23.1) 38 | rubocop (>= 1.48.1, < 2.0) 39 | rubocop-ast (>= 1.31.1, < 2.0) 40 | ruby-progressbar (1.13.0) 41 | standard (1.44.0) 42 | language_server-protocol (~> 3.17.0.2) 43 | lint_roller (~> 1.0) 44 | rubocop (~> 1.70.0) 45 | standard-custom (~> 1.0.0) 46 | standard-performance (~> 1.6) 47 | standard-custom (1.0.2) 48 | lint_roller (~> 1.0) 49 | rubocop (~> 1.50) 50 | standard-performance (1.6.0) 51 | lint_roller (~> 1.1) 52 | rubocop-performance (~> 1.23.0) 53 | syntax_tree (6.2.0) 54 | prettier_print (>= 1.2.0) 55 | unicode-display_width (3.1.4) 56 | unicode-emoji (~> 4.0, >= 4.0.4) 57 | unicode-emoji (4.0.4) 58 | yard (0.9.37) 59 | 60 | PLATFORMS 61 | arm64-darwin-21 62 | ruby 63 | 64 | DEPENDENCIES 65 | minitest (~> 5.0) 66 | rake (~> 13.0) 67 | standard (~> 1.9) 68 | yard-rustdoc! 69 | 70 | BUNDLED WITH 71 | 2.6.3 72 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Jimmy Bourassa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YARD::Rustdoc 2 | 3 | YARD plugin for documenting Magnus-based Rust gems. Supports writing class 4 | documentation on Struct and Enums, and method documentation on Struct and Enum 5 | methods. 6 | 7 | ## Installation 8 | 9 | Add this line to your application's Gemfile: 10 | 11 | ```ruby 12 | gem "yard-rustdoc" 13 | ``` 14 | 15 | Load the plugin through `--plugin rustdoc` (e.g. in your project's `.yardopts`). 16 | See `test/samples/example-ext` for full example. 17 | 18 | ## Usage 19 | 20 | 1. Write YARD-compatible documentation in Rust documentation block 21 | (`///` or `//!`), and tag them with `@yard`. 22 | 23 | 2. Generate Rustdoc as JSON: 24 | 25 | ```sh 26 | cargo +nightly rustdoc -p path/to/extension -- \ 27 | -Zunstable-options --output-format json \ 28 | --document-private-items 29 | ``` 30 | 31 | `nightly` is required because the JSON format isn't stable yet. 32 | `--document-private-items` is included so that you don't have to make 33 | everything in the crate public. 34 | 35 | 3. Run YARD on Ruby files and the Rustdoc's JSON: 36 | 37 | ```sh 38 | yard lib path/to/rustdoc.json 39 | ``` 40 | 41 | ### Writing documentation 42 | 43 | YARD::Rustdoc only targets docblocks starting with `@yard` so that you can 44 | still write Rust-style docblocks for non-Ruby parts of your crate. 45 | 46 | #### Examples 47 | 48 | The class name will be extracted from Magnus' `class =`. 49 | 50 | ```rust 51 | /// @yard 52 | /// High-level documentation for Foo::Bar. 53 | #[magnus(class = "Foo::Bar")] 54 | pub struct Bar { } 55 | ``` 56 | 57 | The `@rename` tag renames the class to `Foo::Baz`. 58 | 59 | ```rust 60 | /// @yard 61 | /// @rename Foo::Baz 62 | pub struct InnerName { } 63 | ``` 64 | 65 | Defines `Foo::Bar.new` -- class method because the first argument isn't `self`. 66 | 67 | ```rust 68 | impl Bar { 69 | /// @yard 70 | /// @return [Foo::Bar] 71 | fn new() -> Self {} 72 | } 73 | ``` 74 | 75 | Defines `Foo::Bar#baz` and `#qux` -- instance method because the method's first 76 | argument is either `&self` or `rb_self`. 77 | 78 | ```rust 79 | impl Bar { 80 | /// @yard 81 | fn baz(&self) {} 82 | 83 | /// @yard 84 | fn qux(rb_self: Value) {} 85 | } 86 | ``` 87 | 88 | Specifies the method's name and params with `@def`. This lets YARD know which params 89 | are required, optional, keyword arguments, etc. 90 | 91 | `@def` must be a single, valid ruby method definition, without the `end`. 92 | 93 | ```rust 94 | impl Bar { 95 | /// @yard 96 | /// @def qux=(val = "") 97 | /// @param val [Object] 98 | fn write_qux(&self, val: Value) {} 99 | } 100 | ``` 101 | 102 | This will be ignored as it's not tagged with `@yard`. 103 | 104 | ```rust 105 | impl Bar { 106 | fn secret {} 107 | } 108 | ``` 109 | 110 | #### Tips 111 | 112 | YARD's syntax differs from what Rustdoc expects. Linters you might want to disable: 113 | 114 | ```rust 115 | #![allow(rustdoc::broken_intra_doc_links)] 116 | #![allow(rustdoc::invalid_html_tags)] 117 | #![allow(rustdoc::bare_urls)] 118 | ``` 119 | 120 | ## Development 121 | 122 | Run tests with `rake`. The tests use a sample project located in 123 | `test/samples/example-ext`. To regenerate its json doc, run `rake doc:rustdoc` 124 | from that directory. See the test project's Rakefile for details. 125 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rake/testtask" 5 | 6 | Rake::TestTask.new(:test) do |t| 7 | t.libs << "test" 8 | t.libs << "lib" 9 | t.test_files = FileList["test/**/*_test.rb"] 10 | puts FileList["test/**/test_*.rb"] 11 | end 12 | 13 | task default: :test 14 | -------------------------------------------------------------------------------- /lib/yard-rustdoc.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "json" 4 | require "yard" 5 | 6 | require_relative "yard-rustdoc/version" 7 | require_relative "yard-rustdoc/parser" 8 | require_relative "yard-rustdoc/statements" 9 | require_relative "yard-rustdoc/handlers" 10 | require_relative "yard-rustdoc/def_parser" 11 | 12 | module YARD 13 | Parser::SourceParser.register_parser_type(:rustdoc, Parser::Rustdoc::Parser, "json") 14 | Handlers::Processor.register_handler_namespace(:rustdoc, Handlers::Rustdoc) 15 | Tags::Library.define_tag("Tagging docblock for yard", :yard) 16 | Tags::Library.define_tag("Renaming class & methods", :rename) 17 | Tags::Library.define_tag("Specify a method name and args", :def) 18 | Tags::Library.define_tag("Specify that a Struct should be a module (instead of class)", :module) 19 | end 20 | -------------------------------------------------------------------------------- /lib/yard-rustdoc/def_parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "syntax_tree" 4 | 5 | module YARD 6 | module Rustdoc 7 | class DefParser 8 | # The name of the `def`'d method 9 | attr_reader(:name) 10 | 11 | # The params of the `def`'d method 12 | # @return [Array<[String, [String, nil]]>] An array of tuple where 0 is 13 | # the name of the arg with its specifier (&, **, :) and 1 is the default. 14 | attr_reader(:parameters) 15 | 16 | def initialize(string) 17 | @string = string 18 | @name = nil 19 | @parameters = [] 20 | parse! 21 | end 22 | 23 | private 24 | 25 | def parse! 26 | string = "def #{@string}; end" 27 | def_node = begin 28 | SyntaxTree.parse(string) # Program 29 | .child_nodes.first # Statements 30 | .child_nodes.first # Def 31 | rescue => e 32 | log.debug(e) 33 | return log.warn("failed to extract def node from '#{@string}'\n #{e.message}") 34 | end 35 | @name = def_node.name.value 36 | 37 | params = def_node.params 38 | params = params.contents if params.is_a?(SyntaxTree::Paren) 39 | 40 | params.requireds.each { |name| add_param(name) } 41 | params.optionals.each { |name, default| add_param(name, default) } 42 | add_param(params.rest) if params.rest 43 | params.posts.each { |name| add_param(name) } 44 | params.keywords.each { |name, default| add_param(name, default) } 45 | add_param(params.keyword_rest) if params.keyword_rest 46 | 47 | add_param(params.block) if params.block 48 | end 49 | 50 | def format(node) 51 | formatter = SyntaxTree::Formatter.new(nil) 52 | node.format(formatter) 53 | formatter.flush 54 | formatter.output 55 | end 56 | 57 | def add_param(node, default = nil) 58 | default = format(default) if default 59 | 60 | @parameters << [format(node), default] 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/yard-rustdoc/handlers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARD::Handlers 4 | module Rustdoc 5 | class Base < YARD::Handlers::Base 6 | include YARD::Parser::Rustdoc 7 | 8 | def self.statement_class(klass = nil) 9 | @statement_classes ||= [] 10 | @statement_classes << klass 11 | 12 | nil 13 | end 14 | 15 | def self.handles?(statement, processor) 16 | handles = true 17 | if @statement_classes.any? 18 | handles &&= @statement_classes.any? { |klass| statement.is_a?(klass) } 19 | end 20 | 21 | handles 22 | end 23 | 24 | def register_file_info(object, file = statement.file, line = statement.line, comments = statement.docstring) 25 | super 26 | end 27 | 28 | def register_docstring(object, docstring = statement.docstring, stmt = statement) 29 | super 30 | end 31 | end 32 | 33 | class StructHandler < Base 34 | statement_class(Statements::Struct) 35 | 36 | process do 37 | obj = statement.code_object_class.new(:root, statement.name) 38 | register(obj) 39 | 40 | push_state(namespace: obj) do 41 | parser.process(statement.methods) 42 | end 43 | end 44 | end 45 | 46 | class MethodHandler < Base 47 | statement_class(Statements::Method) 48 | 49 | process do 50 | obj = MethodObject.new(namespace, statement.name, statement.scope) 51 | obj.parameters = statement.parameters if statement.parameters 52 | register(obj) 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/yard-rustdoc/parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARD::Parser::Rustdoc 4 | class Parser < YARD::Parser::Base 5 | TOP_LEVEL_KINDS = ["struct", "enum"].freeze 6 | 7 | # This default constructor does nothing. The subclass is responsible for 8 | # storing the source contents and filename if they are required. 9 | # @param [String] source the source contents 10 | # @param [String] filename the name of the file if from disk 11 | def initialize(source, filename) 12 | @source = source 13 | @rustdoc_json = JSON.parse(@source).fetch("index") do 14 | raise "Expected `index` top-level key in Rustdoc json format" 15 | end 16 | @filename = filename 17 | @entries = [] 18 | end 19 | 20 | # Override inspect instead of dumping the file content because it is huge. 21 | def inspect 22 | "<#{self.class.name} @filename=#{@filename.inspect}>" 23 | end 24 | 25 | # Finds Rust Struct for the current crate marked with @yard and extract all 26 | # the marked methods. 27 | # @return [Base] this method should return itself 28 | def parse 29 | @entries = [] 30 | 31 | @rustdoc_json.each do |id, entry| 32 | next unless relevant_entry?(entry) 33 | 34 | # "inner" is a Rust enum serialized with serde, resulting in a 35 | # { "variant": { ...variant fields... } } structure. 36 | # See https://github.com/rust-lang/rust/blob/f79a912d9edc3ad4db910c0e93672ed5c65133fa/src/rustdoc-json-types/lib.rs#L104 37 | kind, inner = entry["inner"].first 38 | 39 | next unless TOP_LEVEL_KINDS.include?(kind) 40 | 41 | methods = inner 42 | .fetch("impls") 43 | .flat_map do |impl_id| 44 | @rustdoc_json.dig(impl_id.to_s, "inner", "impl", "items") 45 | end 46 | .filter_map do |method_id| 47 | method_entry = @rustdoc_json.fetch(method_id.to_s) 48 | next unless relevant_entry?(method_entry) 49 | 50 | Statements::Method.new(method_entry) 51 | end 52 | 53 | @entries << Statements::Struct.new(entry, methods) 54 | end 55 | 56 | # Ensure Foo comes before Foo::Bar 57 | @entries.sort_by!(&:name) 58 | 59 | self 60 | end 61 | 62 | def tokenize 63 | raise "Rustdoc Parser does not tokenize" 64 | end 65 | 66 | # This method should be implemented to return a list of semantic tokens 67 | # representing the source code to be post-processed. Otherwise the method 68 | # should return nil. 69 | # 70 | # @abstract 71 | # @return [Array] a list of semantic tokens representing the source code 72 | # to be post-processed 73 | # @return [nil] if no post-processing should be done 74 | def enumerator 75 | @entries 76 | end 77 | 78 | private 79 | 80 | def relevant_entry?(entry) 81 | return false unless entry["crate_id"].zero? 82 | return false unless entry["docs"]&.include?("@yard") 83 | 84 | true 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/yard-rustdoc/statements.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARD::Parser::Rustdoc 4 | module Statements 5 | class Base 6 | def initialize(rustdoc) 7 | @rustdoc = rustdoc 8 | @name = nil 9 | @parameters = [] 10 | end 11 | 12 | def docstring 13 | @docstring ||= rust_docstring 14 | .gsub(/^\s*@yard\s*\n/m, "") # remove @yard line 15 | end 16 | 17 | def source 18 | return if file.nil? 19 | 20 | File.read(file) 21 | .lines 22 | .slice(line_index_range) 23 | .join 24 | rescue Errno::ENOENT 25 | log.warn("can't read '#{file}', cwd='#{Dir.pwd}'") 26 | 27 | "" 28 | end 29 | 30 | def line 31 | line_range.first 32 | end 33 | 34 | def line_range 35 | @rustdoc.dig("span", "begin", 0)...@rustdoc.dig("span", "end", 0) 36 | end 37 | 38 | def file 39 | @rustdoc.dig("span", "filename") 40 | end 41 | 42 | def show 43 | @rustdoc.to_s 44 | end 45 | 46 | # Not sure what should go here either 47 | def comments_hash_flag 48 | end 49 | 50 | def comments_range 51 | end 52 | 53 | private 54 | 55 | def line_index_range 56 | (@rustdoc.dig("span", "begin", 0) - 1)..@rustdoc.dig("span", "end", 0) 57 | end 58 | 59 | def rust_docstring 60 | @rustdoc.fetch("docs") 61 | end 62 | end 63 | 64 | class Struct < Base 65 | attr_reader(:methods) 66 | 67 | def initialize(rustdoc, methods) 68 | super(rustdoc) 69 | @methods = methods 70 | end 71 | 72 | def name 73 | return $1.strip if docstring =~ /^@rename\s*(.+)/ 74 | 75 | @rustdoc["attrs"].each do |attr| 76 | next unless attr.include?("magnus") 77 | 78 | # Extract class name from magnus attrs that define classes such as: 79 | # - #[magnus::wrap(class = "ClassName")] 80 | # - #[magnus(class = "ClassName")] 81 | return $1.strip if attr =~ /class\s*=\s*"([^"]+)"/ 82 | end 83 | 84 | # Fallback to the struct's name 85 | @rustdoc.fetch("name") 86 | end 87 | 88 | def code_object_class 89 | if rust_docstring.match?(/^@module\b/) 90 | YARD::CodeObjects::ModuleObject 91 | else 92 | YARD::CodeObjects::ClassObject 93 | end 94 | end 95 | end 96 | 97 | class Method < Base 98 | def name 99 | parse_def! 100 | 101 | @name || @rustdoc.fetch("name") 102 | end 103 | 104 | # Infers the scope (instance vs class) based on the usage of "self" or 105 | # "rb_self" as an arg name. 106 | def scope 107 | inputs = 108 | # JSON rustdoc FORMAT_VERSION < 34 109 | @rustdoc.dig("inner", "function", "decl", "inputs") || 110 | # >= 34 111 | @rustdoc.dig("inner", "function", "sig", "inputs") 112 | 113 | arg_names = inputs 114 | .map(&:first) 115 | .slice(0, 2) # Magnus may inject a Ruby handle as arg0, hence we check 2 args 116 | 117 | if arg_names.include?("self") || arg_names.include?("rb_self") 118 | :instance 119 | else 120 | :class 121 | end 122 | end 123 | 124 | # Parses the parameters from the @def annotations in the docstring 125 | def parameters 126 | parse_def! 127 | 128 | @parameters 129 | end 130 | 131 | private 132 | 133 | # Extract @def tag from the docstring. Has to be done before we create 134 | # the CodeObject in the `handler` because: 135 | # 1. The method name can be overriden -- it's too late for that once 136 | # the method code object exists. 137 | # 2. The docstring parser runs automatically after the code object is 138 | # created, and emits warning on udnefined `@param`. We need to set the 139 | # method's parameters before then. 140 | def parse_def! 141 | return if defined?(@def_parsed) 142 | @def_parsed = true 143 | 144 | parsed = YARD::DocstringParser.new.parse(@rustdoc.fetch("docs")) 145 | def_tag = parsed.tags.find do |tag| 146 | tag.respond_to?(:tag_name) && tag.tag_name == "def" 147 | end 148 | 149 | return unless def_tag 150 | 151 | parser = YARD::Rustdoc::DefParser.new(def_tag.text) 152 | @name = parser.name 153 | @parameters = parser.parameters || [] 154 | end 155 | end 156 | end 157 | end 158 | -------------------------------------------------------------------------------- /lib/yard-rustdoc/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARD 4 | module Rustdoc 5 | VERSION = "0.4.1" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/integration_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class IntegrationTest < Minitest::Test 6 | SUPPORTED_VERSIONS = ["v33", "v39"].freeze 7 | 8 | def self.test(name, &block) 9 | SUPPORTED_VERSIONS.each do |version| 10 | define_method(:"test_#{name}_#{version}") do 11 | parse_example(version) 12 | instance_exec(&block) 13 | end 14 | end 15 | end 16 | 17 | test("class_can_be_renamed") do 18 | assert_defined("Example::Renamed") 19 | end 20 | 21 | test("method_can_be_renamed") do 22 | assert_defined("Example::Foo#renamed") 23 | end 24 | 25 | test("only_tagged_code_is_documented") do 26 | assert_defined("Example::Foo.new") 27 | assert_defined("Example::Foo#bar") 28 | assert_defined("Example::SomeEnum") 29 | refute_defined("Example::Foo#secret") 30 | refute_defined("Example::Secret") 31 | end 32 | 33 | test("rb_self_as_first_param_defines_instance_method") do 34 | refute_defined("Example::Foo.with_rb_self") 35 | assert_defined("Example::Foo#with_rb_self") 36 | end 37 | 38 | test("self_works_with_ruby_arg") do 39 | refute_defined("Example::Foo.with_ruby_and_rb_self") 40 | assert_defined("Example::Foo#with_ruby_and_rb_self") 41 | end 42 | 43 | test("params_are_extracted_from_def") do 44 | foo_bar = YARD::Registry.at("Example::Foo#bar") 45 | params = foo_bar.parameters.to_h 46 | expected = { 47 | "req" => nil, 48 | "opt" => "[]", 49 | "*args" => nil, 50 | "reqkw:" => nil, 51 | "optkw:" => "nil", 52 | "**kwargs" => nil, 53 | "&block" => nil 54 | } 55 | 56 | assert_equal(expected, params) 57 | end 58 | 59 | test("removes_atyard_tag") do 60 | foobar = YARD::Registry.at("Example::Foo#bar") 61 | refute(foobar.has_tag?("yard"), "@yard tag should be removed") 62 | end 63 | 64 | test("struct_can_be_a_module") do 65 | not_class = YARD::Registry.at("NotClass") 66 | assert_equal(:module, not_class.type) 67 | end 68 | 69 | private 70 | 71 | def assert_defined(id) 72 | refute_nil(YARD::Registry.at(id), "#{id} should be defined") 73 | end 74 | 75 | def refute_defined(id) 76 | assert_nil(YARD::Registry.at(id), "#{id} should not exist") 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /test/samples/example-ext/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | # Without this flag, when linking static libruby, the linker removes symbols 3 | # (such as `_rb_ext_ractor_safe`) which it thinks are dead code... but they are 4 | # not, and they need to be included for the `embed` feature to work with static 5 | # Ruby. 6 | rustflags = ["-C", "link-dead-code=on"] 7 | -------------------------------------------------------------------------------- /test/samples/example-ext/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "bindgen" 16 | version = "0.69.4" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" 19 | dependencies = [ 20 | "bitflags", 21 | "cexpr", 22 | "clang-sys", 23 | "itertools", 24 | "lazy_static", 25 | "lazycell", 26 | "proc-macro2", 27 | "quote", 28 | "regex", 29 | "rustc-hash", 30 | "shlex", 31 | "syn", 32 | ] 33 | 34 | [[package]] 35 | name = "bitflags" 36 | version = "2.6.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 39 | 40 | [[package]] 41 | name = "cexpr" 42 | version = "0.6.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 45 | dependencies = [ 46 | "nom", 47 | ] 48 | 49 | [[package]] 50 | name = "cfg-if" 51 | version = "1.0.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 54 | 55 | [[package]] 56 | name = "clang-sys" 57 | version = "1.8.1" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 60 | dependencies = [ 61 | "glob", 62 | "libc", 63 | "libloading", 64 | ] 65 | 66 | [[package]] 67 | name = "either" 68 | version = "1.13.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 71 | 72 | [[package]] 73 | name = "example" 74 | version = "0.1.0" 75 | dependencies = [ 76 | "magnus", 77 | ] 78 | 79 | [[package]] 80 | name = "glob" 81 | version = "0.3.1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 84 | 85 | [[package]] 86 | name = "itertools" 87 | version = "0.12.1" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 90 | dependencies = [ 91 | "either", 92 | ] 93 | 94 | [[package]] 95 | name = "lazy_static" 96 | version = "1.5.0" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 99 | 100 | [[package]] 101 | name = "lazycell" 102 | version = "1.3.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 105 | 106 | [[package]] 107 | name = "libc" 108 | version = "0.2.158" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" 111 | 112 | [[package]] 113 | name = "libloading" 114 | version = "0.8.5" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" 117 | dependencies = [ 118 | "cfg-if", 119 | "windows-targets", 120 | ] 121 | 122 | [[package]] 123 | name = "magnus" 124 | version = "0.7.1" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "3d87ae53030f3a22e83879e666cb94e58a7bdf31706878a0ba48752994146dab" 127 | dependencies = [ 128 | "magnus-macros", 129 | "rb-sys", 130 | "rb-sys-env", 131 | "seq-macro", 132 | ] 133 | 134 | [[package]] 135 | name = "magnus-macros" 136 | version = "0.6.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "5968c820e2960565f647819f5928a42d6e874551cab9d88d75e3e0660d7f71e3" 139 | dependencies = [ 140 | "proc-macro2", 141 | "quote", 142 | "syn", 143 | ] 144 | 145 | [[package]] 146 | name = "memchr" 147 | version = "2.7.4" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 150 | 151 | [[package]] 152 | name = "minimal-lexical" 153 | version = "0.2.1" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 156 | 157 | [[package]] 158 | name = "nom" 159 | version = "7.1.3" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 162 | dependencies = [ 163 | "memchr", 164 | "minimal-lexical", 165 | ] 166 | 167 | [[package]] 168 | name = "proc-macro2" 169 | version = "1.0.86" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 172 | dependencies = [ 173 | "unicode-ident", 174 | ] 175 | 176 | [[package]] 177 | name = "quote" 178 | version = "1.0.37" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 181 | dependencies = [ 182 | "proc-macro2", 183 | ] 184 | 185 | [[package]] 186 | name = "rb-sys" 187 | version = "0.9.110" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "56cf964f8e44115e50009921ea3d3791b6f74d1ae6d6ed37114fbe03a1cd7308" 190 | dependencies = [ 191 | "rb-sys-build", 192 | ] 193 | 194 | [[package]] 195 | name = "rb-sys-build" 196 | version = "0.9.110" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "161480347f56473107d4135643b6b1909331eec61445e113b256708a28b691c5" 199 | dependencies = [ 200 | "bindgen", 201 | "lazy_static", 202 | "proc-macro2", 203 | "quote", 204 | "regex", 205 | "shell-words", 206 | "syn", 207 | ] 208 | 209 | [[package]] 210 | name = "rb-sys-env" 211 | version = "0.1.2" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "a35802679f07360454b418a5d1735c89716bde01d35b1560fc953c1415a0b3bb" 214 | 215 | [[package]] 216 | name = "regex" 217 | version = "1.10.6" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" 220 | dependencies = [ 221 | "aho-corasick", 222 | "memchr", 223 | "regex-automata", 224 | "regex-syntax", 225 | ] 226 | 227 | [[package]] 228 | name = "regex-automata" 229 | version = "0.4.7" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 232 | dependencies = [ 233 | "aho-corasick", 234 | "memchr", 235 | "regex-syntax", 236 | ] 237 | 238 | [[package]] 239 | name = "regex-syntax" 240 | version = "0.8.4" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 243 | 244 | [[package]] 245 | name = "rustc-hash" 246 | version = "1.1.0" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 249 | 250 | [[package]] 251 | name = "seq-macro" 252 | version = "0.3.5" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" 255 | 256 | [[package]] 257 | name = "shell-words" 258 | version = "1.1.0" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" 261 | 262 | [[package]] 263 | name = "shlex" 264 | version = "1.3.0" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 267 | 268 | [[package]] 269 | name = "syn" 270 | version = "2.0.77" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" 273 | dependencies = [ 274 | "proc-macro2", 275 | "quote", 276 | "unicode-ident", 277 | ] 278 | 279 | [[package]] 280 | name = "unicode-ident" 281 | version = "1.0.13" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 284 | 285 | [[package]] 286 | name = "windows-targets" 287 | version = "0.52.6" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 290 | dependencies = [ 291 | "windows_aarch64_gnullvm", 292 | "windows_aarch64_msvc", 293 | "windows_i686_gnu", 294 | "windows_i686_gnullvm", 295 | "windows_i686_msvc", 296 | "windows_x86_64_gnu", 297 | "windows_x86_64_gnullvm", 298 | "windows_x86_64_msvc", 299 | ] 300 | 301 | [[package]] 302 | name = "windows_aarch64_gnullvm" 303 | version = "0.52.6" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 306 | 307 | [[package]] 308 | name = "windows_aarch64_msvc" 309 | version = "0.52.6" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 312 | 313 | [[package]] 314 | name = "windows_i686_gnu" 315 | version = "0.52.6" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 318 | 319 | [[package]] 320 | name = "windows_i686_gnullvm" 321 | version = "0.52.6" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 324 | 325 | [[package]] 326 | name = "windows_i686_msvc" 327 | version = "0.52.6" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 330 | 331 | [[package]] 332 | name = "windows_x86_64_gnu" 333 | version = "0.52.6" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 336 | 337 | [[package]] 338 | name = "windows_x86_64_gnullvm" 339 | version = "0.52.6" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 342 | 343 | [[package]] 344 | name = "windows_x86_64_msvc" 345 | version = "0.52.6" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 348 | -------------------------------------------------------------------------------- /test/samples/example-ext/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["ext"] 3 | -------------------------------------------------------------------------------- /test/samples/example-ext/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rb_sys", "~> 0.9.18" 6 | gem "rake-compiler", "~> 1.2.0" 7 | gem "yard" 8 | gem "yard-rustdoc", path: "../../../" 9 | -------------------------------------------------------------------------------- /test/samples/example-ext/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: ../../.. 3 | specs: 4 | yard-rustdoc (0.4.0) 5 | syntax_tree (~> 6.0) 6 | yard (~> 0.9) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | prettier_print (1.2.1) 12 | rake (13.2.1) 13 | rake-compiler (1.2.9) 14 | rake 15 | rake-compiler-dock (1.9.1) 16 | rb_sys (0.9.110) 17 | rake-compiler-dock (= 1.9.1) 18 | syntax_tree (6.2.0) 19 | prettier_print (>= 1.2.0) 20 | yard (0.9.37) 21 | 22 | PLATFORMS 23 | arm64-darwin-21 24 | arm64-darwin-23 25 | arm64-darwin-24 26 | 27 | DEPENDENCIES 28 | rake-compiler (~> 1.2.0) 29 | rb_sys (~> 0.9.18) 30 | yard 31 | yard-rustdoc! 32 | 33 | BUNDLED WITH 34 | 2.3.19 35 | -------------------------------------------------------------------------------- /test/samples/example-ext/Rakefile: -------------------------------------------------------------------------------- 1 | require "rake/testtask" 2 | require "rb_sys/extensiontask" 3 | 4 | RbSys::ExtensionTask.new("example") do |ext| 5 | ext.lib_dir = "ext" 6 | end 7 | 8 | namespace :doc do 9 | task default: %i[rustdoc yard] 10 | 11 | desc "Generate Yard documentation" 12 | task :yard do 13 | run("bundle exec yard doc --plugin rustdoc -- lib rustdoc.json") 14 | end 15 | 16 | desc "Generate Rust documentation as JSON" 17 | task :rustdoc do 18 | require "fileutils" 19 | require "json" 20 | 21 | ext_name = "example" 22 | nightly = "+nightly" 23 | nightly += "-#{ENV["NIGHTLY_VERSION"]}" if ENV["NIGHTLY_VERSION"] 24 | 25 | run(<<~CMD) 26 | cargo #{nightly} rustdoc \ 27 | --target-dir tmp \ 28 | -p #{ext_name} \ 29 | -- \ 30 | -Zunstable-options --output-format json --document-private-items 31 | CMD 32 | 33 | rustdoc_path = "tmp/doc/#{ext_name}.json" 34 | version = JSON.load_file(rustdoc_path).fetch("format_version") 35 | 36 | FileUtils.mkdir_p("rustdoc") 37 | run("cp #{rustdoc_path} rustdoc.json") 38 | run("cp #{rustdoc_path} rustdoc/v#{version}.json") 39 | end 40 | 41 | def run(cmd) 42 | system(cmd) 43 | fail if $? != 0 44 | end 45 | end 46 | 47 | task doc: "doc:default" 48 | task default: "doc:default" 49 | -------------------------------------------------------------------------------- /test/samples/example-ext/ext/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.19" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "bindgen" 16 | version = "0.60.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" 19 | dependencies = [ 20 | "bitflags", 21 | "cexpr", 22 | "clang-sys", 23 | "lazy_static", 24 | "lazycell", 25 | "peeking_take_while", 26 | "proc-macro2", 27 | "quote", 28 | "regex", 29 | "rustc-hash", 30 | "shlex", 31 | ] 32 | 33 | [[package]] 34 | name = "bitflags" 35 | version = "1.3.2" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 38 | 39 | [[package]] 40 | name = "cexpr" 41 | version = "0.6.0" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 44 | dependencies = [ 45 | "nom", 46 | ] 47 | 48 | [[package]] 49 | name = "cfg-if" 50 | version = "1.0.0" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 53 | 54 | [[package]] 55 | name = "clang-sys" 56 | version = "1.4.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" 59 | dependencies = [ 60 | "glob", 61 | "libc", 62 | "libloading", 63 | ] 64 | 65 | [[package]] 66 | name = "darling" 67 | version = "0.13.4" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" 70 | dependencies = [ 71 | "darling_core", 72 | "darling_macro", 73 | ] 74 | 75 | [[package]] 76 | name = "darling_core" 77 | version = "0.13.4" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" 80 | dependencies = [ 81 | "fnv", 82 | "ident_case", 83 | "proc-macro2", 84 | "quote", 85 | "strsim", 86 | "syn", 87 | ] 88 | 89 | [[package]] 90 | name = "darling_macro" 91 | version = "0.13.4" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" 94 | dependencies = [ 95 | "darling_core", 96 | "quote", 97 | "syn", 98 | ] 99 | 100 | [[package]] 101 | name = "example" 102 | version = "0.1.0" 103 | dependencies = [ 104 | "magnus", 105 | ] 106 | 107 | [[package]] 108 | name = "fnv" 109 | version = "1.0.7" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 112 | 113 | [[package]] 114 | name = "glob" 115 | version = "0.3.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 118 | 119 | [[package]] 120 | name = "ident_case" 121 | version = "1.0.1" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 124 | 125 | [[package]] 126 | name = "lazy_static" 127 | version = "1.4.0" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 130 | 131 | [[package]] 132 | name = "lazycell" 133 | version = "1.3.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 136 | 137 | [[package]] 138 | name = "libc" 139 | version = "0.2.135" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" 142 | 143 | [[package]] 144 | name = "libloading" 145 | version = "0.7.3" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" 148 | dependencies = [ 149 | "cfg-if", 150 | "winapi", 151 | ] 152 | 153 | [[package]] 154 | name = "linkify" 155 | version = "0.9.0" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "96dd5884008358112bc66093362197c7248ece00d46624e2cf71e50029f8cff5" 158 | dependencies = [ 159 | "memchr", 160 | ] 161 | 162 | [[package]] 163 | name = "magnus" 164 | version = "0.3.2" 165 | source = "git+https://github.com/matsadler/magnus#d6332dec2cfbc45ed4d1885e77dfa1d891e252c7" 166 | dependencies = [ 167 | "magnus-macros", 168 | "rb-sys", 169 | ] 170 | 171 | [[package]] 172 | name = "magnus-macros" 173 | version = "0.1.0" 174 | source = "git+https://github.com/matsadler/magnus#d6332dec2cfbc45ed4d1885e77dfa1d891e252c7" 175 | dependencies = [ 176 | "darling", 177 | "proc-macro2", 178 | "quote", 179 | "syn", 180 | ] 181 | 182 | [[package]] 183 | name = "memchr" 184 | version = "2.5.0" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 187 | 188 | [[package]] 189 | name = "minimal-lexical" 190 | version = "0.2.1" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 193 | 194 | [[package]] 195 | name = "nom" 196 | version = "7.1.1" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" 199 | dependencies = [ 200 | "memchr", 201 | "minimal-lexical", 202 | ] 203 | 204 | [[package]] 205 | name = "peeking_take_while" 206 | version = "0.1.2" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 209 | 210 | [[package]] 211 | name = "proc-macro2" 212 | version = "1.0.47" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" 215 | dependencies = [ 216 | "unicode-ident", 217 | ] 218 | 219 | [[package]] 220 | name = "quote" 221 | version = "1.0.21" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 224 | dependencies = [ 225 | "proc-macro2", 226 | ] 227 | 228 | [[package]] 229 | name = "rb-sys" 230 | version = "0.9.34" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "24ff8b3a4d418f3604ac3781aee54a094f3f9d732fb9a2458f73a3937a9ea918" 233 | dependencies = [ 234 | "rb-sys-build", 235 | ] 236 | 237 | [[package]] 238 | name = "rb-sys-build" 239 | version = "0.9.34" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "d2921dcd727615d4af5a6d39cc3c1c8c9dc8e37bf2b42d44b3e101a6da2bce17" 242 | dependencies = [ 243 | "bindgen", 244 | "linkify", 245 | "regex", 246 | "shell-words", 247 | ] 248 | 249 | [[package]] 250 | name = "regex" 251 | version = "1.6.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 254 | dependencies = [ 255 | "aho-corasick", 256 | "memchr", 257 | "regex-syntax", 258 | ] 259 | 260 | [[package]] 261 | name = "regex-syntax" 262 | version = "0.6.27" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 265 | 266 | [[package]] 267 | name = "rustc-hash" 268 | version = "1.1.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 271 | 272 | [[package]] 273 | name = "shell-words" 274 | version = "1.1.0" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" 277 | 278 | [[package]] 279 | name = "shlex" 280 | version = "1.1.0" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" 283 | 284 | [[package]] 285 | name = "strsim" 286 | version = "0.10.0" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 289 | 290 | [[package]] 291 | name = "syn" 292 | version = "1.0.103" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" 295 | dependencies = [ 296 | "proc-macro2", 297 | "quote", 298 | "unicode-ident", 299 | ] 300 | 301 | [[package]] 302 | name = "unicode-ident" 303 | version = "1.0.5" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 306 | 307 | [[package]] 308 | name = "winapi" 309 | version = "0.3.9" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 312 | dependencies = [ 313 | "winapi-i686-pc-windows-gnu", 314 | "winapi-x86_64-pc-windows-gnu", 315 | ] 316 | 317 | [[package]] 318 | name = "winapi-i686-pc-windows-gnu" 319 | version = "0.4.0" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 322 | 323 | [[package]] 324 | name = "winapi-x86_64-pc-windows-gnu" 325 | version = "0.4.0" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 328 | -------------------------------------------------------------------------------- /test/samples/example-ext/ext/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [lib] 10 | crate-type = ["cdylib"] 11 | 12 | 13 | [dependencies] 14 | magnus = "0.7.1" 15 | -------------------------------------------------------------------------------- /test/samples/example-ext/ext/extconf.rb: -------------------------------------------------------------------------------- 1 | require "mkmf" 2 | require "rb_sys/mkmf" 3 | 4 | create_rust_makefile("example/example") 5 | -------------------------------------------------------------------------------- /test/samples/example-ext/ext/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(rustdoc::broken_intra_doc_links)] 2 | #![allow(rustdoc::invalid_html_tags)] 3 | 4 | use magnus::{define_module, function, method, prelude::*, value, Error, Ruby, Value}; 5 | 6 | /// @yard 7 | #[magnus::wrap(class = "Example::Foo")] 8 | struct Foo {} 9 | 10 | impl Foo { 11 | /// @yard 12 | /// @return Example::Foo 13 | fn new() -> Self { 14 | Self {} 15 | } 16 | 17 | /// @yard 18 | /// @def bar(req, opt = [], *args, reqkw:, optkw: nil, **kwargs, &block) 19 | /// @param req [T] 20 | /// @param opt [T] 21 | /// @param args [T] 22 | /// @param reqkw [T] 23 | /// @param optkw [T] 24 | /// @param kwargs [T] 25 | /// @return [nil] 26 | fn bar(&self, _args: &[Value]) {} 27 | 28 | /// @yard 29 | /// @def renamed 30 | fn baz(&self) {} 31 | 32 | /// @yard 33 | fn with_rb_self(rb_self: Value) -> Value { 34 | rb_self 35 | } 36 | 37 | /// @yard 38 | fn with_ruby_and_rb_self(_ruby: &Ruby, rb_self: Value) -> Value { 39 | rb_self 40 | } 41 | 42 | fn secret(&self) -> () {} 43 | } 44 | 45 | /// @yard 46 | #[magnus::wrap(class = "Example::SomeEnum")] 47 | enum SomeEnum { 48 | _A, 49 | _B, 50 | } 51 | 52 | /// @yard 53 | /// @rename Example::Renamed 54 | #[magnus::wrap(class = "Example::OriginalName")] 55 | struct OriginalName {} 56 | 57 | /// Secret: not documented 58 | #[magnus::wrap(class = "Example::Secret")] 59 | struct Secret {} 60 | 61 | /// @yard 62 | /// @module 63 | #[allow(unused)] 64 | struct NotClass {} 65 | 66 | #[magnus::init] 67 | fn init(ruby: &Ruby) -> Result<(), Error> { 68 | let example_ext = define_module("Example")?; 69 | let foo = example_ext.define_class("Example::Foo", ruby.class_object())?; 70 | foo.define_singleton_method("new", function!(Foo::new, 0))?; 71 | foo.define_method("bar", method!(Foo::bar, -1))?; 72 | foo.define_method("baz", method!(Foo::baz, 0))?; 73 | foo.define_method("secret", method!(Foo::secret, 0))?; 74 | foo.define_method("with_rb_self", method!(Foo::with_rb_self, 0))?; 75 | foo.define_method( 76 | "with_ruby_and_rb_self", 77 | method!(Foo::with_ruby_and_rb_self, 0), 78 | )?; 79 | let _some_enum = example_ext.define_class("Example::SomeEnum", ruby.class_object())?; 80 | Ok(()) 81 | } 82 | -------------------------------------------------------------------------------- /test/samples/example-ext/lib/example.rb: -------------------------------------------------------------------------------- 1 | module Example 2 | end 3 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift File.expand_path("../lib", __dir__) 4 | require "yard" 5 | require "yard-rustdoc" 6 | 7 | require "minitest/autorun" 8 | 9 | class Minitest::Test 10 | private 11 | 12 | def parse_example(version) 13 | Dir.chdir("test/samples/example-ext") do 14 | parse_file("rustdoc/#{version}.json") 15 | end 16 | end 17 | 18 | def parse_file(src) 19 | YARD::Registry.clear 20 | YARD::Parser::SourceParser.parse(src) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /yard-rustdoc.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/yard-rustdoc/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "yard-rustdoc" 7 | spec.version = YARD::Rustdoc::VERSION 8 | spec.licenses = ["MIT"] 9 | spec.authors = ["Jimmy Bourassa"] 10 | spec.email = ["jbourassa@gmail.com"] 11 | 12 | spec.summary = "Generate YARD documentation for Magnus-based Rust gems." 13 | spec.homepage = "https://github.com/oxidize-rb/yard-rustdoc" 14 | spec.required_ruby_version = ">= 2.7.0" 15 | 16 | spec.metadata["homepage_uri"] = spec.homepage 17 | spec.metadata["source_code_uri"] = spec.homepage 18 | 19 | # Specify which files should be added to the gem when it is released. 20 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 21 | spec.files = Dir["{lib}/**/*", "LICENSE", "README.md"] 22 | spec.bindir = "exe" 23 | spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } 24 | spec.require_paths = ["lib"] 25 | 26 | spec.add_dependency "yard", "~> 0.9" 27 | spec.add_dependency "syntax_tree", "~> 6.0" 28 | spec.add_development_dependency "rake", "~> 13.0" 29 | spec.add_development_dependency "minitest", "~> 5.0" 30 | spec.add_development_dependency "standard", "~> 1.9" 31 | end 32 | --------------------------------------------------------------------------------