-
Notifications
You must be signed in to change notification settings - Fork 10
Add Noticed compiler #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9e509d3
59d6d94
0f7c10d
1feca9a
cd491a4
2b24b84
354301f
8e37f8f
8a0dd8f
845fa97
e8e8869
92f4cf2
e61d838
e0abfd4
4e82d8c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| # typed: strict | ||
| # frozen_string_literal: true | ||
|
|
||
| return unless defined?(Noticed::Event) && defined?(Noticed::Ephemeral) | ||
|
|
||
| module Tapioca | ||
| module Dsl | ||
| module Compilers | ||
| # `Tapioca::Dsl::Compilers::Noticed` decorates RBI files for subclasses | ||
| # of `Noticed::Event` and `Noticed::Ephemeral`. | ||
| # | ||
| # For example, with the following notifier class: | ||
| # | ||
| # ~~~rb | ||
| # class NewCommentNotifier < Noticed::Event | ||
| # required_params :comment | ||
| # deliver_by :email | ||
| # end | ||
| # ~~~ | ||
| # | ||
| # This compiler will produce the RBI file `new_comment_notifier.rbi` with the following content: | ||
| # | ||
| # ~~~rbi | ||
| # # new_comment_notifier.rbi | ||
| # # typed: true | ||
| # class NewCommentNotifier | ||
| # class << self | ||
| # sig { params(params: T::Hash[Symbol, T.untyped]).returns(NewCommentNotifier) } | ||
| # def with(params); end | ||
| # | ||
| # sig { params(recipients: T.untyped, enqueue_job: T.nilable(T::Boolean), options: T.untyped).returns(NewCommentNotifier) } | ||
| # def deliver(recipients = T.unsafe(nil), enqueue_job: T.unsafe(nil), **options); end | ||
| # | ||
| # sig { params(recipients: T.untyped, enqueue_job: T.nilable(T::Boolean), options: T.untyped).returns(NewCommentNotifier) } | ||
| # def deliver_later(recipients = T.unsafe(nil), enqueue_job: T.unsafe(nil), **options); end | ||
| # end | ||
| # end | ||
| # ~~~ | ||
| class Noticed < Tapioca::Dsl::Compiler | ||
| extend T::Sig | ||
|
|
||
| ConstantType = type_member do | ||
| { fixed: T.any(T.class_of(::Noticed::Event), T.class_of(::Noticed::Ephemeral)) } | ||
| end | ||
|
|
||
| class << self | ||
| extend T::Sig | ||
|
|
||
| sig { override.returns(T::Enumerable[T::Module[T.anything]]) } | ||
| def gather_constants | ||
| all_classes.select do |klass| | ||
| klass < ::Noticed::Event || klass < ::Noticed::Ephemeral | ||
| end | ||
| end | ||
| end | ||
|
|
||
| sig { override.void } | ||
| def decorate | ||
| root.create_path(constant) do |klass| | ||
| singleton = RBI::SingletonClass.new | ||
| klass << singleton | ||
|
|
||
| singleton.create_method( | ||
| "with", | ||
| parameters: [create_param("params", type: "T::Hash[Symbol, T.untyped]")], | ||
| return_type: "::#{constant}", | ||
| ) | ||
|
|
||
| singleton.create_method( | ||
| "deliver", | ||
| parameters: [ | ||
| create_opt_param("recipients", type: "T.untyped", default: "T.unsafe(nil)"), | ||
| create_kw_opt_param("enqueue_job", type: "T.nilable(T::Boolean)", default: "T.unsafe(nil)"), | ||
| create_kw_rest_param("options", type: "T.untyped"), | ||
| ], | ||
| return_type: "::#{constant}", | ||
| ) | ||
|
|
||
| singleton.create_method( | ||
| "deliver_later", | ||
| parameters: [ | ||
| create_opt_param("recipients", type: "T.untyped", default: "T.unsafe(nil)"), | ||
| create_kw_opt_param("enqueue_job", type: "T.nilable(T::Boolean)", default: "T.unsafe(nil)"), | ||
| create_kw_rest_param("options", type: "T.untyped"), | ||
| ], | ||
| return_type: "::#{constant}", | ||
| ) | ||
|
Comment on lines
69
to
87
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not blocking, but can we use reflection to make any of these types more precise?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. upgraded to within possible options and appropriate class type - but |
||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| ## Noticed | ||
|
|
||
| `Tapioca::Dsl::Compilers::Noticed` decorates RBI files for subclasses | ||
| of `Noticed::Event` and `Noticed::Ephemeral`. | ||
|
|
||
| For example, with the following notifier class: | ||
|
|
||
| ~~~rb | ||
| class NewCommentNotifier < Noticed::Event | ||
| required_params :comment | ||
| deliver_by :email | ||
| end | ||
| ~~~ | ||
|
|
||
| This compiler will produce the RBI file `new_comment_notifier.rbi` with the following content: | ||
|
|
||
| ~~~rbi | ||
| # new_comment_notifier.rbi | ||
| # typed: true | ||
| class NewCommentNotifier | ||
| class << self | ||
| sig { params(params: T::Hash[Symbol, T.untyped]).returns(NewCommentNotifier) } | ||
| def with(params); end | ||
|
|
||
| sig { params(recipients: T.untyped, enqueue_job: T.nilable(T::Boolean), options: T.untyped).returns(NewCommentNotifier) } | ||
| def deliver(recipients = T.unsafe(nil), enqueue_job: T.unsafe(nil), **options); end | ||
|
|
||
| sig { params(recipients: T.untyped, enqueue_job: T.nilable(T::Boolean), options: T.untyped).returns(NewCommentNotifier) } | ||
| def deliver_later(recipients = T.unsafe(nil), enqueue_job: T.unsafe(nil), **options); end | ||
| end | ||
| end | ||
| ~~~ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| # typed: strict | ||
| # frozen_string_literal: true | ||
|
|
||
| require "spec_helper" | ||
|
|
||
| # Stub classes for the Noticed gem. | ||
| # | ||
| # Unlike gems like FlagShihTzu or MoneyRails which just extend ActiveRecord, | ||
| # Noticed is a Rails Engine that requires a full Rails boot. The real | ||
| # Noticed::Event and Noticed::Ephemeral classes: | ||
| # - Are in `app/models/` (Rails autoloading) | ||
| # - Include `Rails.application.routes.url_helpers` | ||
| # - Require `ApplicationRecord` to exist | ||
| # | ||
| # For testing the compiler, we define stub base classes that notifiers can | ||
| # inherit from. Test notifier classes are created using `add_ruby_file` for | ||
| # proper isolation (same pattern as the AR service specs). | ||
| module Noticed | ||
| class Event; end | ||
| class Ephemeral; end | ||
| end | ||
|
|
||
| module Tapioca | ||
| module Dsl | ||
| module Compilers | ||
| class NoticedSpec < ::DslSpec | ||
| describe "Tapioca::Dsl::Compilers::Noticed" do | ||
| describe "initialize" do | ||
| it "gathers no constants if there are no Noticed classes" do | ||
| add_ruby_file("post.rb", <<~RUBY) | ||
| class Post | ||
| end | ||
| RUBY | ||
|
|
||
| assert_equal(gathered_constants, []) | ||
| end | ||
|
|
||
| it "gathers Noticed::Event subclasses" do | ||
| add_ruby_file("new_comment_notifier.rb", <<~RUBY) | ||
| class NewCommentNotifier < Noticed::Event | ||
| end | ||
| RUBY | ||
|
|
||
| assert_equal(["NewCommentNotifier"], gathered_constants) | ||
| end | ||
|
|
||
| it "gathers Noticed::Ephemeral subclasses" do | ||
| add_ruby_file("welcome_notifier.rb", <<~RUBY) | ||
| class WelcomeNotifier < Noticed::Ephemeral | ||
| end | ||
| RUBY | ||
|
|
||
| assert_equal(["WelcomeNotifier"], gathered_constants) | ||
| end | ||
| end | ||
|
|
||
| describe "decorate" do | ||
| it "generates RBI for Noticed::Event subclass" do | ||
| add_ruby_file("new_comment_notifier.rb", <<~RUBY) | ||
| class NewCommentNotifier < Noticed::Event | ||
| end | ||
| RUBY | ||
|
|
||
| expected = <<~RBI | ||
| # typed: strong | ||
|
|
||
| class NewCommentNotifier | ||
| class << self | ||
| sig { params(recipients: T.untyped, enqueue_job: T.nilable(T::Boolean), options: T.untyped).returns(::NewCommentNotifier) } | ||
| def deliver(recipients = T.unsafe(nil), enqueue_job: T.unsafe(nil), **options); end | ||
|
|
||
| sig { params(recipients: T.untyped, enqueue_job: T.nilable(T::Boolean), options: T.untyped).returns(::NewCommentNotifier) } | ||
| def deliver_later(recipients = T.unsafe(nil), enqueue_job: T.unsafe(nil), **options); end | ||
|
|
||
| sig { params(params: T::Hash[Symbol, T.untyped]).returns(::NewCommentNotifier) } | ||
| def with(params); end | ||
| end | ||
| end | ||
| RBI | ||
|
|
||
| assert_equal(expected, rbi_for(:NewCommentNotifier)) | ||
| end | ||
|
|
||
| it "generates RBI for Noticed::Ephemeral subclass" do | ||
| add_ruby_file("welcome_notifier.rb", <<~RUBY) | ||
| class WelcomeNotifier < Noticed::Ephemeral | ||
| end | ||
| RUBY | ||
|
|
||
| expected = <<~RBI | ||
| # typed: strong | ||
|
|
||
| class WelcomeNotifier | ||
| class << self | ||
| sig { params(recipients: T.untyped, enqueue_job: T.nilable(T::Boolean), options: T.untyped).returns(::WelcomeNotifier) } | ||
| def deliver(recipients = T.unsafe(nil), enqueue_job: T.unsafe(nil), **options); end | ||
|
|
||
| sig { params(recipients: T.untyped, enqueue_job: T.nilable(T::Boolean), options: T.untyped).returns(::WelcomeNotifier) } | ||
| def deliver_later(recipients = T.unsafe(nil), enqueue_job: T.unsafe(nil), **options); end | ||
|
|
||
| sig { params(params: T::Hash[Symbol, T.untyped]).returns(::WelcomeNotifier) } | ||
| def with(params); end | ||
| end | ||
| end | ||
| RBI | ||
|
|
||
| assert_equal(expected, rbi_for(:WelcomeNotifier)) | ||
| end | ||
|
|
||
| it "generates RBI for nested notifier classes" do | ||
| add_ruby_file("admin/alert_notifier.rb", <<~RUBY) | ||
| module Admin | ||
| class AlertNotifier < Noticed::Event | ||
| end | ||
| end | ||
| RUBY | ||
|
|
||
| expected = <<~RBI | ||
| # typed: strong | ||
|
|
||
| class Admin::AlertNotifier | ||
| class << self | ||
| sig { params(recipients: T.untyped, enqueue_job: T.nilable(T::Boolean), options: T.untyped).returns(::Admin::AlertNotifier) } | ||
| def deliver(recipients = T.unsafe(nil), enqueue_job: T.unsafe(nil), **options); end | ||
|
|
||
| sig { params(recipients: T.untyped, enqueue_job: T.nilable(T::Boolean), options: T.untyped).returns(::Admin::AlertNotifier) } | ||
| def deliver_later(recipients = T.unsafe(nil), enqueue_job: T.unsafe(nil), **options); end | ||
|
|
||
| sig { params(params: T::Hash[Symbol, T.untyped]).returns(::Admin::AlertNotifier) } | ||
| def with(params); end | ||
| end | ||
| end | ||
| RBI | ||
|
|
||
| assert_equal(expected, rbi_for("Admin::AlertNotifier")) | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end |
Uh oh!
There was an error while loading. Please reload this page.