yard-virtus2 0.0.5

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