yard-virtus2 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +24 -0
- data/.idea/modules.xml +8 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +75 -0
- data/Guardfile +11 -0
- data/LICENSE.txt +19 -0
- data/README.md +61 -0
- data/Rakefile +9 -0
- data/example/README.md +4 -0
- data/example/address.rb +7 -0
- data/example/city.rb +5 -0
- data/example/user.rb +9 -0
- data/lib/yard-virtus.rb +18 -0
- data/lib/yard/virtus/code_objects.rb +2 -0
- data/lib/yard/virtus/code_objects/attribute_reader.rb +26 -0
- data/lib/yard/virtus/code_objects/attribute_writer.rb +48 -0
- data/lib/yard/virtus/declarations.rb +4 -0
- data/lib/yard/virtus/declarations/options.rb +53 -0
- data/lib/yard/virtus/declarations/type.rb +71 -0
- data/lib/yard/virtus/declarations/virtus_attribute.rb +73 -0
- data/lib/yard/virtus/declarations/virtus_model.rb +40 -0
- data/lib/yard/virtus/handlers.rb +2 -0
- data/lib/yard/virtus/handlers/include_virtus_model.rb +32 -0
- data/lib/yard/virtus/handlers/virtus_attribute.rb +58 -0
- data/lib/yard/virtus/mixin_handler_monkey_patch.rb +25 -0
- data/lib/yard/virtus/version.rb +5 -0
- data/spec/code_objects/attribute_reader_spec.rb +38 -0
- data/spec/code_objects/attribute_writer_spec.rb +75 -0
- data/spec/declarations/options_spec.rb +50 -0
- data/spec/declarations/type_spec.rb +37 -0
- data/spec/declarations/virtus_attribute_spec.rb +73 -0
- data/spec/declarations/virtus_model_spec.rb +35 -0
- data/spec/examples/include_virtus_model_001.rb.txt +15 -0
- data/spec/examples/virtus_attribute_001.rb.txt +65 -0
- data/spec/handlers/include_virtus_model_spec.rb +22 -0
- data/spec/handlers/virtus_attribute_spec.rb +67 -0
- data/spec/spec_helper.rb +65 -0
- data/spec/support/helpers/handler_helpers.rb +9 -0
- data/spec/support/helpers/parsing_helpers.rb +13 -0
- data/spec/support/matchers/attribute_matchers.rb +39 -0
- data/spec/support/matchers/method_type_matchers.rb +11 -0
- data/yard-virtus2.gemspec +22 -0
- metadata +119 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
module YARD
|
2
|
+
module Virtus
|
3
|
+
module Declarations
|
4
|
+
# VirtusModel declaration wraps AST which represents
|
5
|
+
# call to `attribute` method.
|
6
|
+
# It's job is to provide information which documents attribute.
|
7
|
+
class VirtusAttribute
|
8
|
+
attr_reader :ast
|
9
|
+
|
10
|
+
# @param [YARD::Parser::Ruby::MethodCallNode] ast
|
11
|
+
def initialize(ast)
|
12
|
+
@ast = ast
|
13
|
+
@options = Options.new(parameters[2])
|
14
|
+
end
|
15
|
+
|
16
|
+
def readable?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def writable?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
# Predicate to check if attribute has private writer.
|
25
|
+
def has_private_writer?
|
26
|
+
options[:writer] == :private
|
27
|
+
end
|
28
|
+
|
29
|
+
# Name of the attribute.
|
30
|
+
# @return [Symbol]
|
31
|
+
def attr_name
|
32
|
+
parameters.first.jump(:ident).first.to_sym
|
33
|
+
end
|
34
|
+
|
35
|
+
# Type of the attribute in YARD format.
|
36
|
+
# @return [String]
|
37
|
+
def type
|
38
|
+
Type.new(type_param).yard_type_string
|
39
|
+
end
|
40
|
+
|
41
|
+
def attribute_reader
|
42
|
+
CodeObjects::AttributeReader.new(attr_name, type)
|
43
|
+
end
|
44
|
+
|
45
|
+
def attribute_writer
|
46
|
+
CodeObjects::AttributeWriter.new(attr_name, type, has_private_writer?)
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
attr_reader :options
|
51
|
+
|
52
|
+
def parameters
|
53
|
+
ast.parameters(false)
|
54
|
+
end
|
55
|
+
|
56
|
+
def type_param
|
57
|
+
parameters[1]
|
58
|
+
end
|
59
|
+
|
60
|
+
def scalar_type_to_string(ref)
|
61
|
+
ref.path.join("::")
|
62
|
+
end
|
63
|
+
|
64
|
+
def collection_type_to_string(type)
|
65
|
+
collection_type = scalar_type_to_string(type_param[0])
|
66
|
+
element_type = scalar_type_to_string(type_param[1].jump(:var_ref))
|
67
|
+
|
68
|
+
"#{collection_type}<#{element_type}>"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module YARD
|
2
|
+
module Virtus
|
3
|
+
module Declarations
|
4
|
+
# VirtusModel declaration wraps AST which represents mixin of Virtus.
|
5
|
+
# It's job is to provide information about Virtus features mixed-in
|
6
|
+
# via Virtus declaration.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # This is AST for mixin source of `include Virtus.model`.
|
10
|
+
# ast = s(:call, s(:var_ref, s(:const, "Virtus")),
|
11
|
+
# :".",
|
12
|
+
# s(:ident, "model"))
|
13
|
+
#
|
14
|
+
# model = YARD::Virtus::Declarations::VirtusModel.new(ast)
|
15
|
+
# model.module_proxies_in_ns(ns) # => P("Virtus.model")
|
16
|
+
class VirtusModel
|
17
|
+
attr_reader :ast
|
18
|
+
|
19
|
+
# @param [YARD::Parser::Ruby::MethodCallNode] ast
|
20
|
+
def initialize(ast)
|
21
|
+
@ast = ast
|
22
|
+
end
|
23
|
+
|
24
|
+
# Get list of proxies to modules which document fetaures inherited
|
25
|
+
# via mixin.
|
26
|
+
#
|
27
|
+
# @param [YARD::CodeObjects::ClassObject] namespace
|
28
|
+
# @return [Array<YARD::CodeObjects::Proxy>]
|
29
|
+
def module_proxies_in_ns(namespace)
|
30
|
+
[YARD::CodeObjects::Proxy.new(namespace, mixin_name, :module)]
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
def mixin_name
|
35
|
+
"%s.%s" % [ast.namespace, ast.method_name].map(&:source)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module YARD
|
2
|
+
module Virtus
|
3
|
+
module Handlers
|
4
|
+
class IncludeVirtusModel < YARD::Handlers::Ruby::Base
|
5
|
+
handles method_call(:include)
|
6
|
+
namespace_only
|
7
|
+
|
8
|
+
def process
|
9
|
+
raise YARD::Handlers::HandlerAborted unless virtus_module?
|
10
|
+
|
11
|
+
declaration = Declarations::VirtusModel.new(virtus_call)
|
12
|
+
declaration.module_proxies_in_ns(namespace).each do |proxy|
|
13
|
+
namespace.mixins(scope).unshift(proxy)
|
14
|
+
end
|
15
|
+
|
16
|
+
namespace[:supports_virtus_attributes] = true
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def virtus_module?
|
22
|
+
included_module = statement.parameters.jump(:var_ref)
|
23
|
+
included_module and included_module.source == "Virtus"
|
24
|
+
end
|
25
|
+
|
26
|
+
def virtus_call
|
27
|
+
statement.parameters(false)[0]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module YARD
|
2
|
+
module Virtus
|
3
|
+
module Handlers
|
4
|
+
class VirtusAttribute < YARD::Handlers::Ruby::Base
|
5
|
+
handles method_call(:attribute)
|
6
|
+
namespace_only
|
7
|
+
|
8
|
+
def process
|
9
|
+
raise YARD::Handlers::HandlerAborted unless virtus_model?
|
10
|
+
|
11
|
+
declaration = Declarations::VirtusAttribute.new(statement)
|
12
|
+
|
13
|
+
|
14
|
+
register_attribute!(declaration.attribute_reader, :read) if declaration.readable?
|
15
|
+
register_attribute!(declaration.attribute_writer, :write) if declaration.writable?
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
# @param [CodeObjects::AttributeReader, CodeObjects::AttributeWriter] mobject
|
21
|
+
# @param [YARD::CodeObjects::MethodObject] mobject
|
22
|
+
# @param [:read, :write] type
|
23
|
+
def register_attribute!(mobject, type)
|
24
|
+
yard_mobject = mobject.yard_method_object(namespace)
|
25
|
+
|
26
|
+
register_preserving_tags!(yard_mobject)
|
27
|
+
attributes_data_for(mobject.attr_name)[type] = yard_mobject
|
28
|
+
end
|
29
|
+
|
30
|
+
def attributes_data_for(name)
|
31
|
+
namespace.attributes[scope][name] ||= SymbolHash[:read => nil, :write => nil]
|
32
|
+
end
|
33
|
+
|
34
|
+
def virtus_model?
|
35
|
+
namespace.inheritance_tree(true).any? { |n| n[:supports_virtus_attributes] == true }
|
36
|
+
end
|
37
|
+
|
38
|
+
# When you register an object it can get assigned docstring which
|
39
|
+
# followed statement which was handled. If such a docstring exists it
|
40
|
+
# can result in removal of previously extracted tags like `@return` and
|
41
|
+
# `@private` as well as default values. To prevent it we restore all
|
42
|
+
# tags which disappeared after registration.
|
43
|
+
#
|
44
|
+
# Do you love uncontrolled mutations as much as I do?
|
45
|
+
def register_preserving_tags!(object)
|
46
|
+
tags_before_registration = object.tags
|
47
|
+
|
48
|
+
register(object)
|
49
|
+
|
50
|
+
lost_tags = tags_before_registration - object.tags
|
51
|
+
if lost_tags.size > 0
|
52
|
+
object.add_tag(*lost_tags)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Standard YARD mixin handler can not process mixins which include method call.
|
2
|
+
# It throws UndocumentableError when it encounters any virtus mixin because they
|
3
|
+
# all include method call:
|
4
|
+
#
|
5
|
+
# [warn]: in YARD::Handlers::Ruby::MixinHandler: Undocumentable mixin: YARD::Parser::UndocumentableError for class City
|
6
|
+
# [warn]: in file 'example/city.rb':2:
|
7
|
+
#
|
8
|
+
# 2: include Virtus.model
|
9
|
+
#
|
10
|
+
# This monkey patch aborts parsing of statement instead of raising UndocumentableError.
|
11
|
+
class YARD::Handlers::Ruby::MixinHandler < YARD::Handlers::Ruby::Base
|
12
|
+
protected
|
13
|
+
alias_method :original_process_mixin, :process_mixin
|
14
|
+
|
15
|
+
def process_mixin(mixin)
|
16
|
+
raise YARD::Handlers::HandlerAborted if virtus_module?(mixin)
|
17
|
+
|
18
|
+
original_process_mixin(mixin)
|
19
|
+
end
|
20
|
+
|
21
|
+
def virtus_module?(mixin)
|
22
|
+
included_module = mixin.jump(:var_ref)
|
23
|
+
included_module and included_module.source == "Virtus"
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe YARD::Virtus::CodeObjects::AttributeReader do
|
4
|
+
subject { described_class.new(:title, "String") }
|
5
|
+
|
6
|
+
it "has #attr_name" do
|
7
|
+
expect(subject.attr_name).to eq(:title)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "has #type" do
|
11
|
+
expect(subject.type).to eq("String")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "has #method_name" do
|
15
|
+
expect(subject.method_name).to eq(:title)
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#yard_method_object" do
|
19
|
+
let(:reader) { described_class.new(:title, "String") }
|
20
|
+
let(:namespace) { YARD::CodeObjects::ClassObject.new(nil, "TemporarySpecClass") }
|
21
|
+
|
22
|
+
subject { reader.yard_method_object(namespace) }
|
23
|
+
|
24
|
+
it { expect(subject).to be_instance_of(YARD::CodeObjects::MethodObject) }
|
25
|
+
|
26
|
+
it "is not explicit" do
|
27
|
+
expect(subject.is_explicit?).to be_false
|
28
|
+
end
|
29
|
+
|
30
|
+
it "has the same name as attribute it reads" do
|
31
|
+
expect(subject.name).to eq(:title)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "has @return tag" do
|
35
|
+
expect(subject.has_tag?(:return)).to be_true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe YARD::Virtus::CodeObjects::AttributeWriter do
|
4
|
+
before :each do
|
5
|
+
# All YARD::CodeObjects::* objects are added to
|
6
|
+
# registry on creation which causes conflicts in
|
7
|
+
# this test.
|
8
|
+
YARD::Registry.clear
|
9
|
+
end
|
10
|
+
|
11
|
+
subject { described_class.new(:title, "String") }
|
12
|
+
|
13
|
+
it "has #attr_name" do
|
14
|
+
expect(subject.attr_name).to eq(:title)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "has #type" do
|
18
|
+
expect(subject.type).to eq("String")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "has #method_name" do
|
22
|
+
expect(subject.method_name).to eq(:"title=")
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#yard_method_object" do
|
26
|
+
let(:writer) { described_class.new(:title, "String") }
|
27
|
+
let(:namespace) { YARD::CodeObjects::ClassObject.new(nil, "TemporarySpecClass") }
|
28
|
+
|
29
|
+
subject { writer.yard_method_object(namespace) }
|
30
|
+
|
31
|
+
it { expect(subject).to be_instance_of(YARD::CodeObjects::MethodObject) }
|
32
|
+
|
33
|
+
it "is not explicit" do
|
34
|
+
expect(subject.is_explicit?).to be_false
|
35
|
+
end
|
36
|
+
|
37
|
+
it "has the writer name for attribute" do
|
38
|
+
expect(subject.name).to eq(:"title=")
|
39
|
+
end
|
40
|
+
|
41
|
+
it "has parameter" do
|
42
|
+
expect(subject.parameters).not_to be_empty
|
43
|
+
end
|
44
|
+
|
45
|
+
it "has type signature tag for parameter" do
|
46
|
+
param_tags = subject.tags(:param).select { |t| t.name == "value" }
|
47
|
+
|
48
|
+
expect(param_tags).not_to be_empty
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when writer visibility is not specified" do
|
52
|
+
let(:writer) { described_class.new(:title, "String") }
|
53
|
+
|
54
|
+
it "does not have @private tag" do
|
55
|
+
expect(subject.tags(:private)).to be_empty
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "when writer is public" do
|
60
|
+
let(:writer) { described_class.new(:title, "String", false) }
|
61
|
+
|
62
|
+
it "does not have @private tag" do
|
63
|
+
expect(subject.tags(:private)).to be_empty
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "when writer is private" do
|
68
|
+
let(:writer) { described_class.new(:title, "String", true) }
|
69
|
+
|
70
|
+
it "has @private tag" do
|
71
|
+
expect(subject.tags(:private)).to have(1).item
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe YARD::Virtus::Declarations::Options do
|
4
|
+
def self.example(src, &block)
|
5
|
+
c = context "'#{src}'"
|
6
|
+
# We can not generate AST for `:a => 1` so we wrap it into
|
7
|
+
# method call like in `attribute :a => 1` and then extract
|
8
|
+
# part corresponding to options. AST for wrapped code will look
|
9
|
+
# like this:
|
10
|
+
#
|
11
|
+
# s(:command,
|
12
|
+
# s(:ident, "attribute"),
|
13
|
+
# s(s(s(:assoc,
|
14
|
+
# s(:symbol_literal, s(:symbol, s(:ident, "a"))),
|
15
|
+
# s(:int, "1"))), false))
|
16
|
+
#
|
17
|
+
c.let(:ast) { ruby_ast("attribute #{src}")[1] }
|
18
|
+
c.class_eval(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
subject { described_class.new(ast) }
|
22
|
+
|
23
|
+
example "" do
|
24
|
+
it { expect(subject).to be_empty }
|
25
|
+
end
|
26
|
+
|
27
|
+
example ":a => :b" do
|
28
|
+
it { expect(subject).not_to be_empty }
|
29
|
+
|
30
|
+
it "has information about key :a" do
|
31
|
+
expect(subject[:a]).to eq(:b)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
example "a: :b" do
|
36
|
+
it { expect(subject).not_to be_empty }
|
37
|
+
|
38
|
+
it "has information about key :a" do
|
39
|
+
expect(subject[:a]).to eq(:b)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
example ":default => lambda { 123 }" do
|
44
|
+
it { expect(subject).not_to be_empty }
|
45
|
+
|
46
|
+
it "has information about lambda function" do
|
47
|
+
expect(subject[:default]).to be_kind_of(Proc)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe YARD::Virtus::Declarations::Type do
|
4
|
+
def self.example(src, &block)
|
5
|
+
c = context "'#{src}'"
|
6
|
+
c.let(:ast) { ruby_ast(src) }
|
7
|
+
c.class_eval(&block)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#yard_type_string" do
|
11
|
+
let(:subject) { described_class.new(ast).yard_type_string }
|
12
|
+
|
13
|
+
example "String" do
|
14
|
+
it { expect(subject).to eq "String" }
|
15
|
+
end
|
16
|
+
|
17
|
+
example "Some::Nested::String" do
|
18
|
+
it { expect(subject).to eq "Some::Nested::String" }
|
19
|
+
end
|
20
|
+
|
21
|
+
example "Array[String]" do
|
22
|
+
it { expect(subject).to eq "Array<String>" }
|
23
|
+
end
|
24
|
+
|
25
|
+
example "Array[Nested::String]" do
|
26
|
+
it { expect(subject).to eq "Array<Nested::String>" }
|
27
|
+
end
|
28
|
+
|
29
|
+
example "Collection[Address]" do
|
30
|
+
it { expect(subject).to eq "Collection<Address>" }
|
31
|
+
end
|
32
|
+
|
33
|
+
example "Hash[String => Integer]" do
|
34
|
+
it { expect(subject).to eq "Hash{String => Integer}" }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|