Skip to content

Commit

Permalink
Add handler for abstract!/interface! modules (#50)
Browse files Browse the repository at this point in the history
* Add abstract handler

* Update documentation

* test interfaces and preserving existing tags

* Rename to AbstractDSLHandler; add class-specific text
  • Loading branch information
dduugg authored May 27, 2021
1 parent 500cd65 commit fbc30ca
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 0 deletions.
1 change: 1 addition & 0 deletions .yardopts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
--main README.md
--private
--plugin sorbet
lib/**/*.rb
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### New features

* [#49](https://github.com/dduugg/yard-sorbet/issues/49) Apply `@abstract` tags to `abstact!`/`interface!` modules
* [#43](https://github.com/dduugg/yard-sorbet/issues/43) Add `T::Enum` support

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

## Install

Expand Down
1 change: 1 addition & 0 deletions lib/yard-sorbet/handlers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# @see https://rubydoc.info/gems/yard/YARD/Handlers/Base YARD Base Handler documentation
module YARDSorbet::Handlers; end

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

# Apllies an +@abstract+ tag to +abstract!+/+interface!+ modules (if not alerady present).
class YARDSorbet::Handlers::AbstractDSLHandler < YARD::Handlers::Ruby::Base
extend T::Sig

handles method_call(:abstract!), method_call(:interface!)
namespace_only

# The text accompanying the `@abstract` tag.
# @see https://github.com/lsegal/yard/blob/main/templates/default/docstring/html/abstract.erb
# The `@abstract` tag template
TAG_TEXT = 'Subclasses must implement the `abstract` methods below.'
# Extra text for class namespaces
CLASS_TAG_TEXT = T.let("This class cannont be directly instantiated. #{TAG_TEXT}", String)

sig { void }
def process
return if namespace.has_tag?(:abstract)

text = namespace.is_a?(YARD::CodeObjects::ClassObject) ? CLASS_TAG_TEXT : TAG_TEXT
tag = YARD::Tags::Tag.new(:abstract, text)
namespace.add_tag(tag)
end
end
2 changes: 2 additions & 0 deletions lib/yard-sorbet/handlers/sig_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ class ParsedSig < T::Struct
prop :return, T.nilable(T::Array[String])
end

# Skip these node types when parsing `sig` params
PARAM_EXCLUDES = T.let(%i[array call hash].freeze, T::Array[Symbol])
# Skip these node types when parsing `sig`s
SIG_EXCLUDES = T.let(%i[array hash].freeze, T::Array[Symbol])

private_constant :ParsedSig, :PARAM_EXCLUDES, :SIG_EXCLUDES
Expand Down
1 change: 1 addition & 0 deletions lib/yard-sorbet/sig_to_yard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
module YARDSorbet::SigToYARD
extend T::Sig

# Ruby 2.5 parsed call nodes slightly differently
IS_LEGACY_RUBY_VERSION = T.let(RUBY_VERSION.start_with?('2.5.'), T::Boolean)
private_constant :IS_LEGACY_RUBY_VERSION

Expand Down
35 changes: 35 additions & 0 deletions spec/data/abstract_dsl_handler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# typed: true
# frozen_string_literal: true

# An abstract class
# @note this class is abstract
class MyAbstractClass
extend T::Helpers
extend T::Sig

abstract!

sig { abstract.void }
def abstract_method; end
end


module MyInterface
extend T::Helpers
extend T::Sig

interface!
sig { abstract.returns(T::Boolean) }
def ibool; end
end


# @abstract Existing abstract tag
module MyTaggedAbstractModule
extend T::Helpers
extend T::Sig

abstract!
sig { abstract.returns(T::Boolean) }
def ibool; end
end
34 changes: 34 additions & 0 deletions spec/data/abstract_handler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# typed: true
# frozen_string_literal: true


module MyInterface
extend T::Helpers
extend T::Sig

interface!
sig { abstract.returns(T::Boolean) }
def ibool; end
end

# An abstract class
# @note this class is abstract
class MyAbstractClass
extend T::Helpers
extend T::Sig

abstract!

sig { abstract.void }
def abstract_method; end
end

# @abstract Existing abstract tag
module MyTaggedAbstractModule
extend T::Helpers
extend T::Sig

abstract!
sig { abstract.returns(T::Boolean) }
def ibool; end
end
41 changes: 41 additions & 0 deletions spec/yard_sorbet/handlers/abstract_dsl_handler_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# typed: false
# frozen_string_literal: true

require 'yard'

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

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

describe 'modules with abstract!/interface! declarations' do
it('apply @abstract tags') do
node = YARD::Registry.at('MyInterface')
expect(node.tags.size).to eq(1)
expect(node.has_tag?(:abstract)).to be(true)
expect(node.tags.first.text).to eq(YARDSorbet::Handlers::AbstractDSLHandler::TAG_TEXT)
end

it('apply class text to abstract classes') do
node = YARD::Registry.at('MyAbstractClass')
expect(node.docstring).to eq('An abstract class')
expect(node.tags.size).to eq(2)
expect(node.has_tag?(:abstract)).to be(true)
abstract_tag = node.tags.find { |tag| tag.tag_name == 'abstract' }
expect(abstract_tag.text).to eq(YARDSorbet::Handlers::AbstractDSLHandler::CLASS_TAG_TEXT)
end

it('keep existing @abstract tags') do
node = YARD::Registry.at('MyTaggedAbstractModule')
expect(node.has_tag?(:abstract)).to be(true)
abstract_tag = node.tags.find { |tag| tag.tag_name == 'abstract' }
expect(abstract_tag.text).to eq('Existing abstract tag')
end
end
end

0 comments on commit fbc30ca

Please sign in to comment.