diff --git a/CHANGELOG.md b/CHANGELOG.md index a7cfd42..1b55390 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## [Unreleased] +- Some refactoring https://github.com/euglena1215/packwerk-yard/pull/11 + ## [0.2.0] - 2024-01-07 - Add packwerk to dependencies https://github.com/euglena1215/packwerk-yard/pull/6 diff --git a/lib/packwerk_yard.rb b/lib/packwerk_yard.rb index c50a44c..48d7aa8 100644 --- a/lib/packwerk_yard.rb +++ b/lib/packwerk_yard.rb @@ -6,6 +6,8 @@ require_relative "packwerk_yard/version" require_relative "packwerk_yard/parser" +require_relative "packwerk_yard/constantize_type" +require_relative "packwerk_yard/yard_handler" module PackwerkYard end diff --git a/lib/packwerk_yard/constantize_type.rb b/lib/packwerk_yard/constantize_type.rb new file mode 100644 index 0000000..ed5b014 --- /dev/null +++ b/lib/packwerk_yard/constantize_type.rb @@ -0,0 +1,43 @@ +# typed: strict +# frozen_string_literal: true + +module PackwerkYard + class ConstantizeType + extend T::Sig + + # Array Syntax e.g. Array + ARRAY_REGEXP = T.let(/\AArray<(.+)>/.freeze, Regexp) + private_constant :ARRAY_REGEXP + + # Hash Syntax e.g. Hash + HASH_REGEXP = T.let(/\AHash<([^,]*),\s?(.*)>/.freeze, Regexp) + private_constant :HASH_REGEXP + + sig { params(yard_type: String).void } + def initialize(yard_type) + @yard_type = yard_type + end + + sig { returns(T::Array[String]) } + def types + split_type(@yard_type).select { |type| constantize?(type) }.uniq + end + + private + + sig { params(name: String).returns(T::Boolean) } + def constantize?(name) + Object.const_get(name) # rubocop:disable Sorbet/ConstantsFromStrings + true + rescue NameError + false + end + + sig { params(type: String).returns(T::Array[String]) } + def split_type(type) + matched_types = Array(ARRAY_REGEXP.match(type).to_a[1]) + matched_types = Array(HASH_REGEXP.match(type).to_a[1..2]) if matched_types.empty? + matched_types.empty? ? [type] : matched_types.map { |t| split_type(t) }.flatten + end + end +end diff --git a/lib/packwerk_yard/parser.rb b/lib/packwerk_yard/parser.rb index f0d1439..3b79795 100644 --- a/lib/packwerk_yard/parser.rb +++ b/lib/packwerk_yard/parser.rb @@ -6,14 +6,6 @@ class Parser extend T::Sig include Packwerk::FileParser - # Array Syntax e.g. Array - ARRAY_REGEXP = T.let(/\AArray<(.+)>/.freeze, Regexp) - private_constant :ARRAY_REGEXP - - # Hash Syntax e.g. Hash - HASH_REGEXP = T.let(/\AHash<([^,]*),\s?(.*)>/.freeze, Regexp) - private_constant :HASH_REGEXP - sig { params(ruby_parser: T.nilable(Packwerk::Parsers::Ruby)).void } def initialize(ruby_parser: Packwerk::Parsers::Ruby.new) @ruby_parser = ruby_parser @@ -24,11 +16,11 @@ def call(io:, file_path: "") source_code = io.read return to_ruby_ast(nil.inspect, file_path) if source_code.nil? - types = extract_from_yard_to_types(source_code) + yard_handler = YardHandler.from_source(source_code) + types = yard_handler.return_types | yard_handler.param_types to_ruby_ast( - types.map { |type| to_evaluable_type(type) }.flatten - .reject { |type| to_constant(type).nil? } + types.map { |type| ConstantizeType.new(type).types }.flatten .inspect.delete('"'), file_path, ) @@ -41,39 +33,6 @@ def match?(path:) private - sig { params(source_code: String).returns(T::Array[String]) } - def extract_from_yard_to_types(source_code) - YARD::Registry.clear - YARD::Logger.instance.enter_level(YARD::Logger::ERROR) do - YARD::Parser::SourceParser.parse_string(source_code) - end - - types = YARD::Registry.all(:method).each_with_object([]) do |method_object, arr| - method_object.tags("param").each do |tag| - arr.concat(tag.types) if tag.types - end - - return_tag = method_object.tag("return") - arr.concat(return_tag.types) if return_tag&.types - end - - types.uniq - end - - sig { params(type: String).returns(T::Array[String]) } - def to_evaluable_type(type) - matched_types = Array(ARRAY_REGEXP.match(type).to_a[1]) - matched_types = Array(HASH_REGEXP.match(type).to_a[1..2]) if matched_types.empty? - matched_types.empty? ? [type] : matched_types.map { |t| to_evaluable_type(t) }.flatten - end - - sig { params(name: T.any(Symbol, String)).returns(T.untyped) } - def to_constant(name) - Object.const_get(name) # rubocop:disable Sorbet/ConstantsFromStrings - rescue NameError - nil - end - sig { params(code: String, file_path: T.untyped).returns(T.untyped) } def to_ruby_ast(code, file_path) T.must(@ruby_parser).call( diff --git a/lib/packwerk_yard/yard_handler.rb b/lib/packwerk_yard/yard_handler.rb new file mode 100644 index 0000000..fb8de7d --- /dev/null +++ b/lib/packwerk_yard/yard_handler.rb @@ -0,0 +1,46 @@ +# typed: strict +# frozen_string_literal: true + +module PackwerkYard + class YardHandler + extend T::Sig + + sig { returns(T::Array[String]) } + attr_reader :return_types + + sig { returns(T::Array[String]) } + attr_reader :param_types + + sig { params(return_types: T::Array[String], param_types: T::Array[String]).void } + def initialize(return_types, param_types) + @return_types = return_types + @param_types = param_types + end + + class << self + extend T::Sig + + sig { params(code: String).returns(YardHandler) } + def from_source(code) + YARD::Registry.clear + YARD::Logger.instance.enter_level(YARD::Logger::ERROR) do + YARD::Parser::SourceParser.parse_string(code) + end + + return_types = [] + param_types = [] + + YARD::Registry.all(:method).each do |method_object| + method_object.tags("param").each do |tag| + param_types << tag.types if tag.types + end + + return_tag = method_object.tag("return") + return_types << return_tag.types if return_tag&.types + end + + new(return_types.flatten.uniq, param_types.flatten.uniq) + end + end + end +end diff --git a/test/fixtures/yard/nested_array.rb b/test/fixtures/yard/nested_array.rb deleted file mode 100644 index 9b5c80f..0000000 --- a/test/fixtures/yard/nested_array.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -class NestedArray - # @param [Array>] array - # @return [Array>] - def nested_array_method(array) - array - end -end diff --git a/test/fixtures/yard/nested_hash.rb b/test/fixtures/yard/nested_hash.rb deleted file mode 100644 index 4718e39..0000000 --- a/test/fixtures/yard/nested_hash.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -class NestedHash - # @param [Hash>] hash - # @return [Hash>] - def nested_hash(hash) - hash - end -end diff --git a/test/fixtures/yard/not_exists_class_args.rb b/test/fixtures/yard/not_exists_class_args.rb deleted file mode 100644 index 602687d..0000000 --- a/test/fixtures/yard/not_exists_class_args.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -class NotExist - # @param [NotExistKlass] a - def not_exist_arg_type(a); end -end diff --git a/test/unit/packwerk_yard/constantize_type_test.rb b/test/unit/packwerk_yard/constantize_type_test.rb new file mode 100644 index 0000000..930d7ef --- /dev/null +++ b/test/unit/packwerk_yard/constantize_type_test.rb @@ -0,0 +1,45 @@ +# typed: false +# frozen_string_literal: true + +require "test_helper" + +module PackwerkYard + class ConstantizeTypeTest < Minitest::Test + def test_types_returns_constantize_types_with_primitive_value + types = PackwerkYard::ConstantizeType.new("String").types + + assert_equal(1, types.size) + assert_equal("String", types[0]) + end + + def test_types_returns_constantize_types_with_simple_array + types = PackwerkYard::ConstantizeType.new("Array").types + + assert_equal(["String"], types) + end + + def test_types_returns_constantize_types_with_nested_array + types = PackwerkYard::ConstantizeType.new("Array>").types + + assert_equal(["String"], types) + end + + def test_types_returns_constantize_types_with_simple_hash + types = PackwerkYard::ConstantizeType.new("Hash").types + + assert_equal(["Integer", "String"], types) + end + + def test_types_returns_constantize_types_with_nested_hash + types = PackwerkYard::ConstantizeType.new("Hash>").types + + assert_equal(["Integer", "String"], types) + end + + def test_types_returns_constantize_types_with_not_existing_class + types = PackwerkYard::ConstantizeType.new("NotExistsClass").types + + assert_equal([], types) + end + end +end diff --git a/test/unit/packwerk_yard/parser_test.rb b/test/unit/packwerk_yard/parser_test.rb index 3ed42ba..711bb21 100644 --- a/test/unit/packwerk_yard/parser_test.rb +++ b/test/unit/packwerk_yard/parser_test.rb @@ -14,36 +14,5 @@ def test_call_returns_node_with_valid_file assert(node.type, :array) assert(node.children[0].children[1], :String) end - - def test_call_return_node_with_not_exists_class_args_file - parser = ::PackwerkYard::Parser.new - io = StringIO.new(File.read("test/fixtures/yard/not_exists_class_args.rb")) - node = parser.call(io: io, file_path: "test/fixtures/yard/not_exists_class_args.rb") - - assert_instance_of(::Parser::AST::Node, node) - assert(node.type, :array) - assert(node.children.size, 0) - end - - def test_call_return_node_with_nested_array_file - parser = ::PackwerkYard::Parser.new - io = StringIO.new(File.read("test/fixtures/yard/nested_array.rb")) - node = parser.call(io: io, file_path: "test/fixtures/yard/nested_array.rb") - - assert_instance_of(::Parser::AST::Node, node) - assert(node.type, :array) - assert(node.children[0].children[1], :String) - end - - def test_call_return_node_with_nested_hash_file - parser = ::PackwerkYard::Parser.new - io = StringIO.new(File.read("test/fixtures/yard/nested_hash.rb")) - node = parser.call(io: io, file_path: "test/fixtures/yard/nested_hash.rb") - - assert_instance_of(::Parser::AST::Node, node) - assert(node.type, :array) - assert(node.children[0].children[1], :String) - assert(node.children[1].children[1], :Integer) - end end end diff --git a/test/unit/packwerk_yard/yard_handler_test.rb b/test/unit/packwerk_yard/yard_handler_test.rb new file mode 100644 index 0000000..0e161a6 --- /dev/null +++ b/test/unit/packwerk_yard/yard_handler_test.rb @@ -0,0 +1,72 @@ +# typed: false +# frozen_string_literal: true + +module PackwerkYard + class YardHandlerTest < Minitest::Test + def test_from_source_with_simple_param_and_return + code = <<~RUBY + class Foo + # @param [String] foo + # @return [String] + def bar(foo) + foo + end + end + RUBY + + handler = PackwerkYard::YardHandler.from_source(code) + + assert_equal(["String"], handler.param_types) + assert_equal(["String"], handler.return_types) + end + + def test_from_source_with_multiple_param + code = <<~RUBY + class Foo + # @param [String] foo + # @param [Integer] bar + def baz(foo, bar) + end + end + RUBY + + handler = PackwerkYard::YardHandler.from_source(code) + + assert_equal(["String", "Integer"], handler.param_types) + end + + def test_from_source_with_nested_array + code = <<~RUBY + class Foo + # @param [Array>] foo + # @return [Array>] + def bar(foo) + foo + end + end + RUBY + + handler = PackwerkYard::YardHandler.from_source(code) + + assert_equal(["Array>"], handler.param_types) + assert_equal(["Array>"], handler.return_types) + end + + def test_from_source_with_polymorphic_param_and_return + code = <<~RUBY + class Foo + # @param [String, Integer] foo + # @return [String, Integer] + def bar(foo) + foo + end + end + RUBY + + handler = PackwerkYard::YardHandler.from_source(code) + + assert_equal(["String", "Integer"], handler.param_types) + assert_equal(["String", "Integer"], handler.return_types) + end + end +end