From 7ef59f0eb7e378f8b39960cf8e6e8b0aa51080fc Mon Sep 17 00:00:00 2001 From: Mateusz Drewniak Date: Mon, 13 May 2024 15:41:30 +0200 Subject: [PATCH] v0.1.4 --- CHANGELOG.md | 4 ++ Gemfile.lock | 2 +- lib/shale/builder/version.rb | 2 +- lib/tapioca/dsl/compilers/shale.rb | 103 +++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 lib/tapioca/dsl/compilers/shale.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e7c20b..4804ef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [0.1.4] - 2024-05-13 + +- Add a tapioca compiler for shale and shale builder + ## [0.1.3] - 2023-11-23 - Change shale version dependency from `< 1.0` to `< 2.0` diff --git a/Gemfile.lock b/Gemfile.lock index bab1c23..4a0d3d9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - shale-builder (0.1.3) + shale-builder (0.1.4) shale (< 2.0) GEM diff --git a/lib/shale/builder/version.rb b/lib/shale/builder/version.rb index 5588cf7..47e64f9 100644 --- a/lib/shale/builder/version.rb +++ b/lib/shale/builder/version.rb @@ -3,6 +3,6 @@ module Shale module Builder # @return [String] - VERSION = '0.1.3' + VERSION = '0.1.4' end end diff --git a/lib/tapioca/dsl/compilers/shale.rb b/lib/tapioca/dsl/compilers/shale.rb new file mode 100644 index 0000000..e565e9d --- /dev/null +++ b/lib/tapioca/dsl/compilers/shale.rb @@ -0,0 +1,103 @@ +# typed: true +# frozen_string_literal: true + +module Tapioca + module Compilers + class Shale < Tapioca::Dsl::Compiler + ConstantType = type_member { { fixed: T.class_of(::Shale::Mapper) } } + + class << self + sig { override.returns(T::Enumerable[Module]) } + def gather_constants + # Collect all the classes that inherit from Shale::Mapper + all_classes.select { |c| c < ::Shale::Mapper } + end + end + + sig { override.void } + def decorate + # Create a RBI definition for each class that inherits from Shale::Mapper + root.create_path(constant) do |klass| + has_shale_builder = includes_shale_builder(constant) + + # For each attribute defined in the class + constant.attributes.each_value do |attribute| + non_nilable_type, nilable_type = shale_type_to_sorbet_type(attribute) + type = nilable_type + if attribute.collection? + type = "T.nilable(T::Array[#{non_nilable_type}])" + end + + if has_shale_builder && attribute.type < ::Shale::Mapper + sigs = T.let([], T::Array[RBI::Sig]) + # simple getter + sigs << klass.create_sig( + parameters: { block: 'NilClass' }, + return_type: type, + ) + # getter with block + sigs << klass.create_sig( + parameters: { block: "T.proc.params(arg0: #{non_nilable_type}).void" }, + return_type: non_nilable_type + ) + klass.create_method_with_sigs( + attribute.name, + sigs: sigs, + parameters: [RBI::BlockParam.new('block')], + ) + else + klass.create_method(attribute.name, return_type: type) + end + + # setter + klass.create_method( + "#{attribute.name}=", + parameters: [create_param('value', type: type)], + return_type: type, + ) + end + end + + end + + private + + sig { params(klass: Class).returns(T.nilable(T::Boolean)) } + def includes_shale_builder(klass) + return false unless defined?(::Shale::Builder) + + klass < ::Shale::Builder + end + + SHALE_TYPES_MAP = T.let( + { + ::Shale::Type::Value => Object, + ::Shale::Type::String => String, + ::Shale::Type::Float => Float, + ::Shale::Type::Integer => Integer, + ::Shale::Type::Time => Time, + ::Shale::Type::Date => Date, + }.freeze, + T::Hash[Class, Class], + ) + + sig { params(attribute: ::Shale::Attribute).returns([String, String]) } + def shale_type_to_sorbet_type(attribute) + return_type = SHALE_TYPES_MAP[attribute.type] + return complex_shale_type_to_sorbet_type(attribute) unless return_type + return [T.must(return_type.name), T.must(return_type.name)] if attribute.collection? || attribute.default.is_a?(return_type) + + [T.must(return_type.name), "T.nilable(#{return_type.name})"] + end + + sig { params(attribute: ::Shale::Attribute).returns([String, String]) } + def complex_shale_type_to_sorbet_type(attribute) + return [T.cast(attribute.type.to_s, String), "T.nilable(#{attribute.type})"] unless attribute.type.respond_to?(:return_type) + + return_type_string = attribute.type.return_type.to_s + [return_type_string, return_type_string] + end + + end + end +end