Skip to content

Commit

Permalink
Extract DslCompiler helper from DslSpec base class
Browse files Browse the repository at this point in the history
  • Loading branch information
paracycle committed Feb 3, 2022
1 parent 15e49b4 commit 480845f
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 107 deletions.
122 changes: 122 additions & 0 deletions lib/tapioca/helpers/test/dsl_compiler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# typed: strict
# frozen_string_literal: true

require "tapioca/helpers/test/content"
require "tapioca/helpers/test/isolation"
require "tapioca/helpers/test/template"

module Tapioca
module Helpers
module Test
module DslCompiler
extend T::Sig
extend T::Helpers

include Isolation
include Content
include Template

requires_ancestor { Kernel }

sig { params(compiler_class: T.class_of(Tapioca::Compilers::Dsl::Base)).void }
def use_dsl_compiler(compiler_class)
@context = T.let(CompilerContext.new(compiler_class), T.nilable(CompilerContext))
end

sig { params(compiler_classes: T.class_of(Tapioca::Compilers::Dsl::Base)).void }
def activate_other_dsl_compilers(*compiler_classes)
context.activate_other_dsl_compilers(compiler_classes)
end

sig { params(constant_name: T.any(Symbol, String)).returns(String) }
def rbi_for(constant_name)
context.rbi_for(constant_name)
end

sig { returns(T::Array[String]) }
def gathered_constants
context.gathered_constants
end

sig { returns(T::Array[String]) }
def generated_errors
context.errors
end

sig { returns(CompilerContext) }
def context
raise "Please call `use_dsl_compiler` before" unless @context
@context
end

class CompilerContext
extend T::Sig

sig { returns(T.class_of(Tapioca::Compilers::Dsl::Base)) }
attr_reader :compiler_class

sig { returns(T::Array[T.class_of(Tapioca::Compilers::Dsl::Base)]) }
attr_reader :other_compiler_classes

sig { params(compiler_class: T.class_of(Tapioca::Compilers::Dsl::Base)).void }
def initialize(compiler_class)
@compiler_class = compiler_class
@other_compiler_classes = T.let([], T::Array[T.class_of(Tapioca::Compilers::Dsl::Base)])
@compiler = T.let(nil, T.nilable(Tapioca::Compilers::Dsl::Base))
@pipeline = T.let(nil, T.nilable(Tapioca::Compilers::DslCompiler))
end

sig { params(compiler_classes: T::Array[T.class_of(Tapioca::Compilers::Dsl::Base)]).void }
def activate_other_dsl_compilers(compiler_classes)
@other_compiler_classes = compiler_classes
end

sig { returns(T::Array[T.class_of(Tapioca::Compilers::Dsl::Base)]) }
def activated_compiler_classes
[compiler_class, *other_compiler_classes]
end

sig { returns(T::Array[String]) }
def gathered_constants
compiler.processable_constants.map(&:name).compact.sort
end

sig { params(constant_name: T.any(Symbol, String)).returns(String) }
def rbi_for(constant_name)
# Make sure this is a constant that we can handle.
unless gathered_constants.include?(constant_name.to_s)
raise "`#{constant_name}` is not processable by the `#{compiler_class}` generator."
end

file = RBI::File.new(strictness: "strong")
constant = Object.const_get(constant_name)

compiler.decorate(file.root, constant)

file.transformed_string
end

sig { returns(T::Array[String]) }
def errors
compiler.errors
end

private

sig { returns(Tapioca::Compilers::Dsl::Base) }
def compiler
@compiler ||= T.must(pipeline.generators.grep(compiler_class).first)
end

sig { returns(Tapioca::Compilers::DslCompiler) }
def pipeline
@pipeline ||= Tapioca::Compilers::DslCompiler.new(
requested_constants: [],
requested_generators: activated_compiler_classes
)
end
end
end
end
end
end
13 changes: 12 additions & 1 deletion lib/tapioca/helpers/test/template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ module Tapioca
module Helpers
module Test
module Template
include Kernel
extend T::Sig
extend T::Helpers

requires_ancestor { Kernel }

ERB_SUPPORTS_KVARGS = T.let(
::ERB.instance_method(:initialize).parameters.assoc(:key), T.nilable([Symbol, Symbol])
)
Expand All @@ -28,6 +31,14 @@ def template(src)

erb.result(binding)
end

sig { params(str: String, indent: Integer).returns(String) }
def indented(str, indent)
str.lines.map! do |line|
next line if line.chomp.empty?
(" " * indent) + line
end.join
end
end
end
end
Expand Down
103 changes: 23 additions & 80 deletions spec/dsl_spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,72 +3,53 @@

require "sorbet-runtime"
require "minitest/spec"
require "tapioca/helpers/test/content"
require "tapioca/helpers/test/template"
require "tapioca/helpers/test/isolation"
require "tapioca/helpers/test/dsl_compiler"

class DslSpec < Minitest::Spec
extend T::Sig
include Kernel
include Tapioca::Helpers::Test::Content
include Tapioca::Helpers::Test::Template
include Tapioca::Helpers::Test::Isolation
include Tapioca::Helpers::Test::DslCompiler

sig { void }
def after_setup
before do
# Require the file that the target class should be loaded from
require(T.unsafe(self).target_class_file)
end

sig { void }
def teardown
super
T.unsafe(self).subject.errors.clear
require(self.class.target_class_file)
use_dsl_compiler(self.class.target_class)
end

subject do
T.bind(self, DslSpec)
# Get the class under test and initialize a new instance of it as the "subject"
generator_for_names(target_class_name)
end

sig { params(names: String).returns(Tapioca::Compilers::Dsl::Base) }
def generator_for_names(*names)
raise "name is required" if names.empty?

classes = names.map { |class_name| Object.const_get(class_name) }

compiler = Tapioca::Compilers::DslCompiler.new(
requested_constants: [],
requested_generators: classes
)

T.must(compiler.generators.find { |generator| generator.class.name == names.first })
after do
generated_errors.clear
end

sig { returns(Class) }
def spec_test_class
# Find the spec test class
klass = T.unsafe(self).class
def self.spec_test_class
# It should be the one that directly inherits from DslSpec
klass = klass.superclass while klass.superclass != DslSpec
klass
class_ancestors = T.cast(ancestors.grep(Class), T::Array[Class])

klass = class_ancestors
.take_while { |ancestor| ancestor != DslSpec }
.last

T.must(klass)
end

sig { returns(String) }
def target_class_name
def self.target_class_name
# Get the name of the class under test from the name of the
# test class
T.must(spec_test_class.name).gsub(/Spec$/, "")
end

sig { returns(T.class_of(Tapioca::Compilers::Dsl::Base)) }
def self.target_class
Object.const_get(target_class_name)
end

sig { returns(String) }
def target_class_file
def self.target_class_file
underscore(target_class_name)
end

sig { params(class_name: String).returns(String) }
def underscore(class_name)
def self.underscore(class_name)
return class_name unless /[A-Z-]|::/.match?(class_name)

word = class_name.to_s.gsub("::", "/")
Expand All @@ -79,44 +60,6 @@ def underscore(class_name)
word
end

sig { params(str: String, indent: Integer).returns(String) }
def indented(str, indent)
str.lines.map! do |line|
next line if line.chomp.empty?
(" " * indent) + line
end.join
end

sig { returns(T::Array[String]) }
def gathered_constants
T.unsafe(self).subject.processable_constants.map(&:name).sort
end

sig do
params(
constant_name: T.any(Symbol, String)
).returns(String)
end
def rbi_for(constant_name)
# Make sure this is a constant that we can handle.
assert_includes(gathered_constants, constant_name.to_s, <<~MSG)
`#{constant_name}` is not processable by the `#{target_class_name}` generator.
MSG

file = RBI::File.new(strictness: "strong")

constant = Object.const_get(constant_name)

T.unsafe(self).subject.decorate(file.root, constant)

file.transformed_string
end

sig { returns(T::Array[String]) }
def generated_errors
T.unsafe(self).subject.errors
end

sig { void }
def assert_no_generated_errors
T.unsafe(self).assert_empty(generated_errors)
Expand Down
12 changes: 4 additions & 8 deletions spec/tapioca/compilers/dsl/active_record_associations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,9 @@ class Current < ActiveRecord::Base
end

describe "with relations enabled" do
subject do
T.bind(self, DslSpec)
before do
require "tapioca/compilers/dsl/active_record_relations"
generator_for_names(target_class_name, "Tapioca::Compilers::Dsl::ActiveRecordRelations")
activate_other_dsl_compilers(Tapioca::Compilers::Dsl::ActiveRecordRelations)
end

describe "without errors" do
Expand Down Expand Up @@ -1437,13 +1436,10 @@ def shop=(value); end
end

describe "decorate_active_storage" do
subject do
T.bind(self, DslSpec)
before do
require "tapioca/compilers/dsl/active_record_relations"
generator_for_names(target_class_name, "Tapioca::Compilers::Dsl::ActiveRecordRelations")
end
activate_other_dsl_compilers(Tapioca::Compilers::Dsl::ActiveRecordRelations)

before do
add_ruby_file("application.rb", <<~RUBY)
ENV["DATABASE_URL"] = "sqlite3::memory:"
Expand Down
16 changes: 6 additions & 10 deletions spec/tapioca/compilers/dsl/active_record_scope_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@ class User
end

describe "with relations enabled" do
subject do
T.bind(self, DslSpec)
before do
require "tapioca/compilers/dsl/active_record_relations"
generator_for_names(target_class_name, "Tapioca::Compilers::Dsl::ActiveRecordRelations")
activate_other_dsl_compilers(Tapioca::Compilers::Dsl::ActiveRecordRelations)
end

it "generates an empty RBI file for ActiveRecord classes with no scope field" do
Expand Down Expand Up @@ -344,17 +343,14 @@ def post_scope(*args, &blk); end
end

describe "decorate_active_storage" do
subject do
T.bind(self, DslSpec)
require "tapioca/compilers/dsl/active_record_relations"
generator_for_names(target_class_name, "Tapioca::Compilers::Dsl::ActiveRecordRelations")
end

after do
T.unsafe(self).assert_no_generated_errors
assert_no_generated_errors
end

before do
require "tapioca/compilers/dsl/active_record_relations"
activate_other_dsl_compilers(Tapioca::Compilers::Dsl::ActiveRecordRelations)

require "active_record"
require "active_storage/attached"
require "active_storage/reflection"
Expand Down
7 changes: 3 additions & 4 deletions spec/tapioca/compilers/dsl/active_record_typed_store_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@

class Tapioca::Compilers::Dsl::ActiveRecordTypedStoreSpec < DslSpec
describe "Tapioca::Compilers::Dsl::ActiveRecordTypedStore" do
before do
add_ruby_file("require.rb", <<~RUBY)
require "active_record"
RUBY
sig { void }
def before_setup
require "active_record"
end

describe "initialize" do
Expand Down
5 changes: 3 additions & 2 deletions spec/tapioca/compilers/dsl/config_spec.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "spec_helper"

class Tapioca::Compilers::Dsl::ConfigSpec < DslSpec
describe "Tapioca::Compilers::Dsl::Config" do
before do
sig { void }
def before_setup
Object.send(:remove_const, :Rails)
end

Expand Down
3 changes: 2 additions & 1 deletion spec/tapioca/compilers/dsl/frozen_record_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

class Tapioca::Compilers::Dsl::FrozenRecordSpec < DslSpec
describe "Tapioca::Compilers::Dsl::FrozenRecord" do
before do
sig { void }
def before_setup
require "rails/railtie"
require "tapioca/compilers/dsl/extensions/frozen_record"
end
Expand Down
Loading

0 comments on commit 480845f

Please sign in to comment.