yard-virtus2 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|