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
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe YARD::Virtus::Declarations::VirtusAttribute do
|
4
|
+
let(:ast) { ruby_ast(declaration) }
|
5
|
+
let(:namespace) { YARD::CodeObjects::ClassObject.new(nil, "TemporarySpecClass") }
|
6
|
+
let(:subject) { described_class.new(ast) }
|
7
|
+
|
8
|
+
def self.example(src, &block)
|
9
|
+
c = context "'#{src}'"
|
10
|
+
c.let(:declaration) { src }
|
11
|
+
c.class_eval(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
example "attribute :title, String" do
|
15
|
+
it { expect(subject).to be_readable }
|
16
|
+
it { expect(subject).to be_writable }
|
17
|
+
|
18
|
+
it { expect(subject.attr_name).to eq(:title) }
|
19
|
+
it { expect(subject.type).to eq("String") }
|
20
|
+
|
21
|
+
it "produces reader method object" do
|
22
|
+
expect(subject.attribute_reader).to be_kind_of(YARD::Virtus::CodeObjects::AttributeReader)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "produces writer method object" do
|
26
|
+
expect(subject.attribute_writer).to be_kind_of(YARD::Virtus::CodeObjects::AttributeWriter)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
example "attribute :title, Namespaced::String" do
|
31
|
+
it { expect(subject).to be_readable }
|
32
|
+
it { expect(subject).to be_writable }
|
33
|
+
|
34
|
+
it { expect(subject.attr_name).to eq(:title) }
|
35
|
+
it { expect(subject.type).to eq("Namespaced::String") }
|
36
|
+
end
|
37
|
+
|
38
|
+
example "attribute :locations, Array[Address]" do
|
39
|
+
it { expect(subject).to be_readable }
|
40
|
+
it { expect(subject).to be_writable }
|
41
|
+
|
42
|
+
it { expect(subject.attr_name).to eq(:locations) }
|
43
|
+
it { expect(subject.type).to eq("Array<Address>") }
|
44
|
+
end
|
45
|
+
|
46
|
+
example "attribute :locations, Array[Array[Point]]" do
|
47
|
+
it { expect(subject).to be_readable }
|
48
|
+
it { expect(subject).to be_writable }
|
49
|
+
|
50
|
+
it { expect(subject.attr_name).to eq(:locations) }
|
51
|
+
it { expect(subject.type).to eq("Array<Array<Point>>") }
|
52
|
+
end
|
53
|
+
|
54
|
+
example "attribute :locations, Hash[Symbol => Address]" do
|
55
|
+
it { expect(subject).to be_readable }
|
56
|
+
it { expect(subject).to be_writable }
|
57
|
+
|
58
|
+
it { expect(subject.attr_name).to eq(:locations) }
|
59
|
+
it { expect(subject.type).to eq("Hash{Symbol => Address}") }
|
60
|
+
end
|
61
|
+
|
62
|
+
example "attribute :unique_id, String, :writer => :private" do
|
63
|
+
it { expect(subject).to have_private_writer }
|
64
|
+
end
|
65
|
+
|
66
|
+
example "attribute :unique_id, String, writer: :private" do
|
67
|
+
it { expect(subject).to have_private_writer }
|
68
|
+
end
|
69
|
+
|
70
|
+
example "attribute :unique_id, String, :writer => :private, :a => :b" do
|
71
|
+
it { expect(subject).to have_private_writer }
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe YARD::Virtus::Declarations::VirtusModel do
|
4
|
+
let(:ast) { ruby_ast(declaration) }
|
5
|
+
let(:namespace) { YARD::CodeObjects::ClassObject.new(nil, "TemporarySpecClass") }
|
6
|
+
let(:subject) { described_class.new(ast) }
|
7
|
+
|
8
|
+
context "when declaration is 'Virtus.model'" do
|
9
|
+
let(:declaration) { "Virtus.model" }
|
10
|
+
|
11
|
+
it "has one module proxy" do
|
12
|
+
expect(subject.module_proxies_in_ns(namespace)).to have(1).item
|
13
|
+
end
|
14
|
+
|
15
|
+
it "has module proxy for Virtus.model" do
|
16
|
+
expect(subject.module_proxies_in_ns(namespace)).to include(P("Virtus.model"))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when declaration is 'Virtus.value_object'" do
|
21
|
+
let(:declaration) { "Virtus.value_object" }
|
22
|
+
|
23
|
+
it "has module proxy for Virtus.value_object" do
|
24
|
+
expect(subject.module_proxies_in_ns(namespace)).to include(P("Virtus.value_object"))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when declaration is 'Virtus.model(mass_assignmet: false)'" do
|
29
|
+
let(:declaration) { "Virtus.model(mass_assignment: false)" }
|
30
|
+
|
31
|
+
it "has module proxy for Virtus.model" do
|
32
|
+
expect(subject.module_proxies_in_ns(namespace)).to include(P("Virtus.model"))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class Address
|
2
|
+
attribute :city, String
|
3
|
+
end
|
4
|
+
|
5
|
+
class User
|
6
|
+
include Virtus.model
|
7
|
+
|
8
|
+
attribute :name, String
|
9
|
+
attribute :age, Integer
|
10
|
+
|
11
|
+
attribute :friends, Array[User]
|
12
|
+
attribute :addresses, Hash[Symbol => Address]
|
13
|
+
|
14
|
+
attribute :unique_id, String, :writer => :private
|
15
|
+
|
16
|
+
# keeps extracted type if comment is present
|
17
|
+
attribute :email, Email
|
18
|
+
|
19
|
+
# preserves private attribute if comment is present
|
20
|
+
attribute :unique_uuid, String, :writer => :private
|
21
|
+
end
|
22
|
+
|
23
|
+
class Page
|
24
|
+
attribute :title, String
|
25
|
+
|
26
|
+
# default from a singleton value (integer in this case)
|
27
|
+
attribute :views, Integer, :default => 0
|
28
|
+
|
29
|
+
# default from a singleton value (boolean in this case)
|
30
|
+
attribute :published, Boolean, :default => false
|
31
|
+
|
32
|
+
# default from a callable object (proc in this case)
|
33
|
+
attribute :slug, String, :default => lambda { |page, attribute| page.title.downcase.gsub(' ', '-') }
|
34
|
+
|
35
|
+
# default from a method name as symbol
|
36
|
+
attribute :editor_title, String, :default => :default_editor_title
|
37
|
+
end
|
38
|
+
|
39
|
+
class BaseVirtusModel
|
40
|
+
include Virtus.model
|
41
|
+
end
|
42
|
+
|
43
|
+
class City < BaseVirtusModel
|
44
|
+
attribute :name, String
|
45
|
+
end
|
46
|
+
|
47
|
+
# Mixin is not defined yet which can
|
48
|
+
# happen in real world setup when files are
|
49
|
+
# scanned in the order which does not match
|
50
|
+
# their dependencies order.
|
51
|
+
class District
|
52
|
+
include ModelMixin
|
53
|
+
|
54
|
+
attribute :city, City
|
55
|
+
end
|
56
|
+
|
57
|
+
module ModelMixin
|
58
|
+
include Virtus.model
|
59
|
+
end
|
60
|
+
|
61
|
+
class Country
|
62
|
+
include ModelMixin
|
63
|
+
|
64
|
+
attribute :cities, Array[City]
|
65
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe YARD::Virtus::Handlers::IncludeVirtusModel, type: :handler do
|
4
|
+
before(:all) { parse_file! :include_virtus_model_001 }
|
5
|
+
|
6
|
+
it "processes basic model declaration" do
|
7
|
+
expect(YARD::Registry.at(:ModelA).instance_mixins).to include(P("Virtus.model"))
|
8
|
+
end
|
9
|
+
|
10
|
+
it "processes value object delcaration" do
|
11
|
+
expect(YARD::Registry.at(:ModelB).instance_mixins).to include(P("Virtus.value_object"))
|
12
|
+
end
|
13
|
+
|
14
|
+
it "processes model declaration with parameters" do
|
15
|
+
expect(YARD::Registry.at(:ModelC).instance_mixins).to include(P("Virtus.model"))
|
16
|
+
end
|
17
|
+
|
18
|
+
it "marks model namespace as supporting virtus attributes" do
|
19
|
+
namespace = YARD::Registry.at(:ModelA)
|
20
|
+
expect(namespace[:supports_virtus_attributes]).to be_true
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe YARD::Virtus::Handlers::VirtusAttribute, type: :handler do
|
4
|
+
before(:all) { parse_file! :virtus_attribute_001 }
|
5
|
+
|
6
|
+
it "does not parse attribute if namespace does not include Virtus declaration" do
|
7
|
+
expect(YARD::Registry.at(:Address)).not_to define_readable_attribute(:city)
|
8
|
+
expect(YARD::Registry.at(:Address)).not_to define_writable_attribute(:city)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "parses attribute name" do
|
12
|
+
expect(YARD::Registry.at(:User)).to define_readable_attribute(:name)
|
13
|
+
expect(YARD::Registry.at(:User)).to define_writable_attribute(:name)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "parses attribute with scalar type" do
|
17
|
+
expect(YARD::Registry.at("User#name")).to have_return_type("String")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "parses attribute with collection type" do
|
21
|
+
expect(YARD::Registry.at("User#friends")).to have_return_type("Array<User>")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "parses attribute with collection map type" do
|
25
|
+
expect(YARD::Registry.at("User#addresses")).to have_return_type("Hash{Symbol => Address}")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "parses and marks attributes with private writers" do
|
29
|
+
expect(YARD::Registry.at("User#unique_id=")).to have_private_writer_api
|
30
|
+
end
|
31
|
+
|
32
|
+
it "parses type of attribute with attached docstring" do
|
33
|
+
expect(YARD::Registry.at("User#email")).to have_return_type("Email")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "parses information about private writers with attached docstring" do
|
37
|
+
expect(YARD::Registry.at("User#unique_uuid=")).to have_private_writer_api
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
context "when namespace becomes virtus model via inheritance chain" do
|
42
|
+
context "from parent class" do
|
43
|
+
it "parses attribute declaration" do
|
44
|
+
expect(YARD::Registry.at(:City)).to define_readable_attribute(:name)
|
45
|
+
expect(YARD::Registry.at(:City)).to define_writable_attribute(:name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "from mixin" do
|
50
|
+
context "and mixin have already been defined when processing declaration" do
|
51
|
+
it "parses attribute declaration" do
|
52
|
+
expect(YARD::Registry.at(:Country)).to define_readable_attribute(:cities)
|
53
|
+
expect(YARD::Registry.at(:Country)).to define_writable_attribute(:cities)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "and mixin have not been defined when processing delcaration" do
|
58
|
+
it "parses attribute declaration" do
|
59
|
+
pending
|
60
|
+
|
61
|
+
expect(YARD::Registry.at(:District)).to define_readable_attribute(:city)
|
62
|
+
expect(YARD::Registry.at(:District)).to define_writable_attribute(:city)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require "stringio"
|
2
|
+
|
3
|
+
_dir = File.dirname(__FILE__)
|
4
|
+
|
5
|
+
# Requires supporting ruby files with custom matchers and macros, etc,
|
6
|
+
# in spec/support/ and its subdirectories.
|
7
|
+
Dir[File.join(_dir, "support", "**", "*.rb")].each do |f|
|
8
|
+
require f
|
9
|
+
end
|
10
|
+
|
11
|
+
require File.join(_dir, "..", "lib", "yard-virtus")
|
12
|
+
|
13
|
+
# Shortcut for creating a YARD::CodeObjects::Proxy via a path
|
14
|
+
#
|
15
|
+
# @see YARD::CodeObjects::Proxy
|
16
|
+
# @see YARD::Registry.resolve
|
17
|
+
def P(namespace, name = nil, type = nil)
|
18
|
+
namespace, name = nil, namespace if name.nil?
|
19
|
+
YARD::Registry.resolve(namespace, name, false, true, type)
|
20
|
+
end
|
21
|
+
|
22
|
+
RSpec.configure do |config|
|
23
|
+
# Run specs in random order to surface order dependencies. If you find an
|
24
|
+
# order dependency and want to debug it, you can fix the order by providing
|
25
|
+
# the seed, which is printed after each run.
|
26
|
+
# --seed 1234
|
27
|
+
config.order = "random"
|
28
|
+
config.color = true
|
29
|
+
|
30
|
+
config.include VirtusYARD::Spec::HandlerHelpers, :type => :handler
|
31
|
+
config.include VirtusYARD::Spec::ParsingHelpers
|
32
|
+
|
33
|
+
config.before(:all) { YARD::Logger.instance(StringIO.new) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_file!(file, thisfile = __FILE__, log_level = log.level, ext = '.rb.txt')
|
37
|
+
YARD::Registry.clear
|
38
|
+
path = File.join(File.dirname(thisfile), 'examples', file.to_s + ext)
|
39
|
+
YARD::Parser::SourceParser.parse(path, [], log_level)
|
40
|
+
end
|
41
|
+
|
42
|
+
class StubbedProcessor < YARD::Handlers::Processor
|
43
|
+
def process(statements)
|
44
|
+
statements.each_with_index do |stmt, index|
|
45
|
+
find_handlers(stmt).each do |handler|
|
46
|
+
handler.new(self, stmt).process
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class StubbedSourceParser < YARD::Parser::SourceParser
|
53
|
+
StubbedSourceParser.parser_type = :ruby
|
54
|
+
def post_process
|
55
|
+
post = StubbedProcessor.new(self)
|
56
|
+
post.process(@parser.enumerator)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def with_parser(parser_type, &block)
|
61
|
+
tmp = StubbedSourceParser.parser_type
|
62
|
+
StubbedSourceParser.parser_type = parser_type
|
63
|
+
yield
|
64
|
+
StubbedSourceParser.parser_type = tmp
|
65
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
RSpec::Matchers.define :define_readable_attribute do |attr_name|
|
2
|
+
match do |namespace|
|
3
|
+
expect(namespace).to be_kind_of(YARD::CodeObjects::NamespaceObject)
|
4
|
+
|
5
|
+
rname = namespace.to_s + "#" + attr_name.to_s
|
6
|
+
reader_object = YARD::Registry.at(rname)
|
7
|
+
attributes_map = namespace.attributes[:instance][attr_name]
|
8
|
+
|
9
|
+
expect(reader_object).to be_instance_of(YARD::CodeObjects::MethodObject)
|
10
|
+
expect(attributes_map[:read]).to eq(reader_object)
|
11
|
+
end
|
12
|
+
|
13
|
+
description do |attr_name|
|
14
|
+
"define readable attribute #{attr_name} in #{namespace.source}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
RSpec::Matchers.define :define_writable_attribute do |attr_name|
|
19
|
+
match do |namespace|
|
20
|
+
expect(namespace).to be_kind_of(YARD::CodeObjects::NamespaceObject)
|
21
|
+
|
22
|
+
wname = namespace.to_s + "#" + attr_name.to_s + "="
|
23
|
+
writer_object = YARD::Registry.at(wname)
|
24
|
+
attributes_map = namespace.attributes[:instance][attr_name]
|
25
|
+
|
26
|
+
expect(writer_object).to be_instance_of(YARD::CodeObjects::MethodObject)
|
27
|
+
expect(attributes_map[:write]).to eq(writer_object)
|
28
|
+
end
|
29
|
+
|
30
|
+
description do
|
31
|
+
"define writable attribute #{attr_name}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
RSpec::Matchers.define :have_private_writer_api do
|
36
|
+
match do |method_object|
|
37
|
+
expect(method_object.tags(:private).size).to be > 0
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# @example
|
2
|
+
# expect(YARD::Registry.at("User#friends")).to have_return_type("Array<User>")
|
3
|
+
RSpec::Matchers.define :have_return_type do |yard_type_string|
|
4
|
+
match do |method_object|
|
5
|
+
return_tags = method_object.tags(:return)
|
6
|
+
|
7
|
+
expect(return_tags.size).to be > 0
|
8
|
+
|
9
|
+
expect(return_tags.any? { |tag| tag.types.include?(yard_type_string) }).to be_true
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path("../lib/yard/virtus/version", __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "yard-virtus2"
|
7
|
+
gem.version = YARD::Virtus::VERSION
|
8
|
+
gem.authors = ["Dmitry Dzema", "Daniel Orner"]
|
9
|
+
gem.email = ["dimad.ag@gmail.com", "dmorner@gmail.com"]
|
10
|
+
gem.homepage = "https://github.com/dorner/yard-virtus"
|
11
|
+
gem.summary = "This library provides handlers to YARD so it can extract information about Virtus attributes like types and visibility"
|
12
|
+
gem.description = gem.summary
|
13
|
+
gem.license = "MIT"
|
14
|
+
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.files = `git ls-files`.split("\n")
|
17
|
+
gem.test_files = `git ls-files -- {spec}/*`.split("\n")
|
18
|
+
gem.extra_rdoc_files = %w[LICENSE.txt README.md]
|
19
|
+
|
20
|
+
gem.add_dependency("virtus", "= 1.0.2")
|
21
|
+
gem.add_dependency("yard")
|
22
|
+
end
|