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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -0
  3. data/.idea/modules.xml +8 -0
  4. data/Gemfile +17 -0
  5. data/Gemfile.lock +75 -0
  6. data/Guardfile +11 -0
  7. data/LICENSE.txt +19 -0
  8. data/README.md +61 -0
  9. data/Rakefile +9 -0
  10. data/example/README.md +4 -0
  11. data/example/address.rb +7 -0
  12. data/example/city.rb +5 -0
  13. data/example/user.rb +9 -0
  14. data/lib/yard-virtus.rb +18 -0
  15. data/lib/yard/virtus/code_objects.rb +2 -0
  16. data/lib/yard/virtus/code_objects/attribute_reader.rb +26 -0
  17. data/lib/yard/virtus/code_objects/attribute_writer.rb +48 -0
  18. data/lib/yard/virtus/declarations.rb +4 -0
  19. data/lib/yard/virtus/declarations/options.rb +53 -0
  20. data/lib/yard/virtus/declarations/type.rb +71 -0
  21. data/lib/yard/virtus/declarations/virtus_attribute.rb +73 -0
  22. data/lib/yard/virtus/declarations/virtus_model.rb +40 -0
  23. data/lib/yard/virtus/handlers.rb +2 -0
  24. data/lib/yard/virtus/handlers/include_virtus_model.rb +32 -0
  25. data/lib/yard/virtus/handlers/virtus_attribute.rb +58 -0
  26. data/lib/yard/virtus/mixin_handler_monkey_patch.rb +25 -0
  27. data/lib/yard/virtus/version.rb +5 -0
  28. data/spec/code_objects/attribute_reader_spec.rb +38 -0
  29. data/spec/code_objects/attribute_writer_spec.rb +75 -0
  30. data/spec/declarations/options_spec.rb +50 -0
  31. data/spec/declarations/type_spec.rb +37 -0
  32. data/spec/declarations/virtus_attribute_spec.rb +73 -0
  33. data/spec/declarations/virtus_model_spec.rb +35 -0
  34. data/spec/examples/include_virtus_model_001.rb.txt +15 -0
  35. data/spec/examples/virtus_attribute_001.rb.txt +65 -0
  36. data/spec/handlers/include_virtus_model_spec.rb +22 -0
  37. data/spec/handlers/virtus_attribute_spec.rb +67 -0
  38. data/spec/spec_helper.rb +65 -0
  39. data/spec/support/helpers/handler_helpers.rb +9 -0
  40. data/spec/support/helpers/parsing_helpers.rb +13 -0
  41. data/spec/support/matchers/attribute_matchers.rb +39 -0
  42. data/spec/support/matchers/method_type_matchers.rb +11 -0
  43. data/yard-virtus2.gemspec +22 -0
  44. 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,15 @@
1
+ class A
2
+ include Model
3
+ end
4
+
5
+ class ModelA
6
+ include Virtus.model
7
+ end
8
+
9
+ class ModelB
10
+ include Virtus.value_object
11
+ end
12
+
13
+ class ModelC
14
+ include Virtus.model(mass_assignmet: false)
15
+ 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
@@ -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,9 @@
1
+ module VirtusYARD
2
+ module Spec
3
+ module HandlerHelpers
4
+ def r(key)
5
+ YARD::P(key)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module VirtusYARD
2
+ module Spec
3
+ module ParsingHelpers
4
+ def parser_for(code)
5
+ YARD::Parser::Ruby::RubyParser.new(code, "virtus-yard-spec.rb")
6
+ end
7
+
8
+ def ruby_ast(statement)
9
+ parser_for(statement).parse.ast[0]
10
+ end
11
+ end
12
+ end
13
+ 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