我正在一个实验性的开源项目(ruby_crystal_codemod)中试用Sorbet。我不知道如何让类型检查在嵌套测试项目中的一些RSpec测试中工作。当我运行srb tc
时,我看到一些类型检查错误,如下所示:
spec/src/example_class_annotated_spec.rb:6: Method it does not exist on T.class_of(<root>) https://srb.help/7003
6 | it 'should add @foo and @bar' do
7 | instance = ExampleClass.new(2, 3, 4)
8 | expect(instance.add).to eq 5
9 | end
spec/src/example_class_annotated_spec.rb:8: Method expect does not exist on T.class_of(<root>) https://srb.help/7003
8 | expect(instance.add).to eq 5
^^^^^^^^^^^^^^^^^^^^
https://github.com/sorbet/sorbet/tree/67cd17f5168252fdec1ad04839b31fdda8bc6155/rbi/core/kernel.rbi#L2662: Did you mean: Kernel#exec?
2662 | def exec(*args); end
^^^^^^^^^^^^^^^
spec/src/example_class_annotated_spec.rb:8: Method eq does not exist on T.class_of(<root>) https://srb.help/7003
8 | expect(instance.add).to eq 5
^^^^
# etc.
这是source directory for the nested project on GitHub。
您应该能够运行以下命令来重现类型错误:
cd /tmp
git clone https://github.com/DocSpring/ruby_crystal_codemod.git
cd ruby_crystal_codemod
git checkout sorbet-rspec-type-checking-error
cd spec/fixtures/rspec_project/
bundle install
bundle exec srb tc
您应该看到这些类型错误:
spec/src/example_class_annotated_spec.rb:6: Method it does not exist on T.class_of(<root>) https://srb.help/7003
6 | it 'should add @foo and @bar' do
7 | instance = ExampleClass.new(2, 3, 4)
8 | expect(instance.add).to eq 5
9 | end
# etc.
spec/fixtures/rspec_project/sorbet/rbi/gems/rspec-core.rbi
等处的RBI文件是否有问题?
2条答案
按热度按时间rsaldnfx1#
也许使用tapioca生成类型定义会对https://github.com/Shopify/tapioca有所帮助
7nbnzgx92#
生成的RBI文件没有任何问题。
RSpec非常依赖DSL。当您调用像
RSpec.describe
这样的方法时,它接受一个块,RSpec
将在特定范围内使用特定绑定执行该块,允许您调用RSpec测试DSL。对于Sorbet来说,要获得RSpec方法的知识,它需要知道这种行为。您可以通过多种方式提示Sorbet有关方法接收的代码块将在其中执行的绑定。您可以在Sorbet文档中阅读它。
如果你看一下你提到的RBI文件,它不包括这些信息。gem的自动RBI生成器通常仅限于确定gem中存在哪些常量(类,模块)和哪些方法。这不包括这些方法或其参数的签名。
如果没有关于某个块的绑定的提示,则Sorbet假定该块绑定到该块的外部上下文。例如,如果您在Ruby的顶层使用
RSpec.describe
,这就是Sorbet将为传递给describe
的块假定的上下文。由于在全局Ruby作用域中没有eq
的定义,因此类型检查将失败。为了解决这个问题,你可以做很多事情,包括不同程度的努力和回报。
1.**不要在你的测试中使用果汁冰糕。**有些人会争辩说测试不需要打字。在不得不在非常大的单体应用程序中重构大量代码之后,我强烈反对这种做法,但事实是这是最轻松的解决方案。只需将
# typed: ignore
放在所有规范文件的开头,然后继续。您将错过测试套件中静态类型的所有好处。1.**创建一个shim,提示Sorbet传递给
RSpec.describe
的块应该绑定到T.untyped
。**我过去成功地使用了这个解决方案。您不会得到Sorbet的所有好处,特别是在RSpec方法上,但至少在调用自己的代码时可以进行类型检查。下面是一个例子,说明这是什么样子。这将允许您在spec文件上使用# typed: true
。1.**创建一个shim,将RSpec中的每个方法绑定到正确的类。**这将花费很长时间,无疑是一个非常大的努力,但如果你设法做到这一点,你将获得几乎所有使用RSpec的Sorbet的好处。你可以share it with the community。这可能还需要为Tapioca编译器编写代码,为使用
let(:something)
定义的内容生成适当的类型。我不相信1和3是一个很大的工作,所以现在我将详细说明如何进行第二个选项。
在
sorbet/rbi/shims/rspec.rbi
中为rspec创建一个shim文件,如下所示:在这个文件中,我们将添加必要的签名,以不假设任何关于传递给RSpec方法的块的绑定。例如,让我们从
describe
开始。如果你去自动生成的RBI,你会看到它有这样的声明:让我们将其复制到我们自己的RBI文件中,并添加我们自己的签名。
我们现在已经告诉Sorbet,你传递给
describe
的块绑定到T.untyped
。此类型充当了退出窗口,允许任何方法调用,因此Sorbet不会抱怨其中缺少方法。您可能需要对其他方法重复此操作,甚至在不同的RSpec类中,直到您完全说服类型检查器不再打扰您。这样你就可以在spec文件中使用
# typed: true
了。它并不完美,实际上它只不过是一个补丁,但它至少会授予您一些非常基本的类型检查功能。