Skip to content

Commit

Permalink
Add mixes_in_class_methods support (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
dduugg authored Oct 12, 2021
1 parent 3fe73b6 commit f0d3c5d
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## main

* [#77](https://github.com/dduugg/yard-sorbet/pull/77) Add `mixes_in_class_methods` support

## 0.5.3 (2021-08-01)

### Bug fixes
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ A YARD [plugin](https://rubydoc.info/gems/yard/file/docs/GettingStarted.md#Plugi
- Generates method definitions from `T::Struct` fields
- Generates constant definitions from `T::Enum` enums
- Modules marked `abstract!` or `interface!` are tagged `@abstract`
- Modules using `mixes_in_class_methods` will attach class methods

## Usage

Expand Down
2 changes: 2 additions & 0 deletions lib/yard-sorbet/handlers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ module Handlers; end

require_relative 'handlers/abstract_dsl_handler'
require_relative 'handlers/enums_handler'
require_relative 'handlers/include_handler'
require_relative 'handlers/mixes_in_class_methods_handler'
require_relative 'handlers/sig_handler'
require_relative 'handlers/struct_class_handler'
require_relative 'handlers/struct_prop_handler'
37 changes: 37 additions & 0 deletions lib/yard-sorbet/handlers/include_handler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# typed: strict
# frozen_string_literal: true

module YARDSorbet
module Handlers
# Extends any modules included via `mixes_in_class_methods`
# @see https://sorbet.org/docs/abstract#interfaces-and-the-included-hook
# Sorbet `mixes_in_class_methods` documentation
class IncludeHandler < YARD::Handlers::Ruby::Base
extend T::Sig

handles method_call(:include)
namespace_only

sig { void }
def process
return unless extra_state.mix_in_class_methods

statement.parameters(false).each do |mixin|
obj = YARD::CodeObjects::Proxy.new(namespace, mixin.source)
class_methods_namespace = extra_state.mix_in_class_methods[obj.to_s]
next unless class_methods_namespace

included_in.mixins(:class) << YARD::CodeObjects::Proxy.new(obj, class_methods_namespace)
end
end

private

# @return the namespace object that is including the module
sig { returns(YARD::CodeObjects::NamespaceObject) }
def included_in
statement.namespace ? YARD::CodeObjects::Proxy.new(namespace, statement.namespace.source) : namespace
end
end
end
end
22 changes: 22 additions & 0 deletions lib/yard-sorbet/handlers/mixes_in_class_methods_handler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# typed: strict
# frozen_string_literal: true

module YARDSorbet
module Handlers
# Tracks modules that invoke `mixes_in_class_methods` for use in {IncludeHandler}
# @see https://sorbet.org/docs/abstract#interfaces-and-the-included-hook
# Sorbet `mixes_in_class_methods` documentation
class MixesInClassMethodsHandler < YARD::Handlers::Ruby::Base
extend T::Sig

handles method_call(:mixes_in_class_methods)
namespace_only

sig { void }
def process
extra_state.mix_in_class_methods ||= {}
extra_state.mix_in_class_methods[namespace.to_s] = statement.parameters(false)[0].source
end
end
end
end
63 changes: 63 additions & 0 deletions spec/data/include_handler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# typed: true
module M
extend T::Helpers
interface!

module ClassMethods
extend T::Sig
extend T::Helpers
abstract!

sig {void}
def foo
bar
end

sig {abstract.void}
def bar; end
end

mixes_in_class_methods(ClassMethods)
end

class A # error: Missing definition for abstract method
include M

extend T::Sig

sig {override.void}
def self.bar; end
end

# Sorbet knows that `foo` is a class method on `A`
A.foo

module Receiver; end

Receiver.include(M)

module OuterModule
module InnerModule
extend T::Helpers
module ClassMethods
def foo; end
end
mixes_in_class_methods(ClassMethods)
end

class InnerClass
include InnerModule
end
end

module M2; end
module M3; end

class C
extend T::Sig

include M2, M, M3

sig {override.void}
def self.bar; end
end
33 changes: 33 additions & 0 deletions spec/yard_sorbet/handlers/include_handler_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# typed: strict
# frozen_string_literal: true

RSpec.describe YARDSorbet::Handlers::IncludeHandler do
path = File.join(File.expand_path('../../data', __dir__), 'include_handler.rb')

before do
YARD::Registry.clear
YARD::Parser::SourceParser.parse(path)
end

describe 'including a module with `mixes_in_class_methods`' do
it 'adds the class method namespace to `class_mixins`' do
node = YARD::Registry.at('A')
expect(node.class_mixins.map(&:to_s)).to include('M::ClassMethods')
end

it 'attches class method namespace to explicit receiver' do
node = YARD::Registry.at('Receiver')
expect(node.class_mixins.map(&:to_s)).to include('M::ClassMethods')
end

it 'resolves full class method namespace' do
node = YARD::Registry.at('OuterModule::InnerClass')
expect(node.class_mixins.map(&:to_s)).to include('OuterModule::InnerModule::ClassMethods')
end

it 'handles multiple parameters to include' do
node = YARD::Registry.at('C')
expect(node.class_mixins.map(&:to_s)).to include('M::ClassMethods')
end
end
end

0 comments on commit f0d3c5d

Please sign in to comment.