-
Notifications
You must be signed in to change notification settings - Fork 24
Skip RBS rewriter when file does not contain RBS syntax #917
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -7,6 +7,33 @@ module Translate | |
| class RBSCommentsToSorbetSigs < Translator | ||
| include Spoom::RBS::ExtractRBSComments | ||
|
|
||
| RBS_ANNOTATION_MARKERS = [ | ||
| "# @abstract", | ||
| "# @interface", | ||
| "# @sealed", | ||
| "# @final", | ||
| "# @requires_ancestor:", | ||
| "# @override", | ||
| "# @overridable", | ||
|
Comment on lines
+16
to
+17
Contributor
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. Random thought I had: does factoring out the common prefixes produce a more faster regular expression? E.g. In short, yes, but barely. Onigmo doesn't optimize out common prefixes in the NFA it creates, but it doesn't make a real world difference here. Got Claude to benchmark it for me: require "benchmark"
# loading corpus... 101996 files, 616MB, loaded in 15.09s
# sanity-checking match equivalence... 19104 total matches across corpus
#
# # user system total real
# flat 0.536375 0.006953 0.543328 ( 0.552176)
# factored 0.530740 0.005332 0.536072 ( 0.544158)
#
# factored is 1.01x faster than flat (real time)
MARKERS = [
"# @abstract",
"# @interface",
"# @sealed",
"# @final",
"# @requires_ancestor:",
"# @override",
"# @overridable",
"# @without_runtime",
].freeze
FLAT = Regexp.union(*MARKERS)
FACTORED = /\# @(?:abstract|interface|sealed|final|requires_ancestor:|overrid(?:e|able)|without_runtime)/
raise "regexes diverge on hits" unless MARKERS.all? { |m| FLAT =~ m && FACTORED =~ m }
raise "regexes diverge on misses" if FLAT =~ "# @other" || FACTORED =~ "# @other"
CORPUS_GLOB = "/your/test/corpus/**/*.rb"
print "loading corpus... "
load_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
CORPUS = Dir.glob(CORPUS_GLOB).map { |path| File.read(path) rescue nil }.compact.freeze
load_elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - load_start
total_bytes = CORPUS.sum(&:bytesize)
puts "#{CORPUS.size} files, #{total_bytes / 1_000_000}MB, loaded in #{load_elapsed.round(2)}s"
print "sanity-checking match equivalence... "
flat_hits = CORPUS.sum { |s| s.scan(FLAT).size }
factored_hits = CORPUS.sum { |s| s.scan(FACTORED).size }
raise "regexes diverge: flat=#{flat_hits} factored=#{factored_hits}" unless flat_hits == factored_hits
puts "#{flat_hits} total matches across corpus"
flat_time = Benchmark.measure("flat") { CORPUS.each { |s| s.scan(FLAT) } }
factored_time = Benchmark.measure("factored") { CORPUS.each { |s| s.scan(FACTORED) } }
slower, faster = [flat_time, factored_time].sort_by(&:real).reverse
ratio = slower.real / faster.real
faster_name = faster.equal?(flat_time) ? "flat" : "factored"
slower_name = slower.equal?(flat_time) ? "flat" : "factored"
puts <<~RESULTS
#{Benchmark::CAPTION}
flat #{flat_time.to_s.strip}
factored #{factored_time.to_s.strip}
#{faster_name} is #{ratio.round(2)}x faster than #{slower_name} (real time)
RESULTS
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.
Interesting! |
||
| "# @without_runtime", | ||
| ].freeze #: Array[String] | ||
| RBS_REWRITE_PATTERN = Regexp.union(["#:", "#|", *RBS_ANNOTATION_MARKERS]).freeze #: Regexp | ||
| private_constant :RBS_ANNOTATION_MARKERS, :RBS_REWRITE_PATTERN | ||
|
|
||
| class << self | ||
| #: (String source) -> bool | ||
| def contains_rbs_syntax?(source) | ||
| Sigils.contains_valid_sigil?(source) && source.match?(RBS_REWRITE_PATTERN) | ||
| end | ||
|
|
||
| #: (String ruby_contents, file: String, ?max_line_length: Integer?) -> String | ||
| def rewrite_if_needed(ruby_contents, file:, max_line_length: nil) | ||
| return ruby_contents unless contains_rbs_syntax?(ruby_contents) | ||
|
|
||
| new(ruby_contents, file:, max_line_length:).rewrite | ||
| end | ||
| end | ||
|
|
||
| #: (String, file: String, ?max_line_length: Integer?) -> void | ||
| def initialize(ruby_contents, file:, max_line_length: nil) | ||
| super(ruby_contents, file: file) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2823,6 +2823,9 @@ module Spoom::Sorbet::Sigils | |
| sig { params(path_list: T::Array[::String], new_strictness: ::String).returns(T::Array[::String]) } | ||
| def change_sigil_in_files(path_list, new_strictness); end | ||
|
|
||
| sig { params(content: ::String).returns(T::Boolean) } | ||
| def contains_valid_sigil?(content); end | ||
|
|
||
| sig { params(path: T.any(::Pathname, ::String)).returns(T.nilable(::String)) } | ||
| def file_strictness(path); end | ||
|
|
||
|
|
@@ -2938,8 +2941,19 @@ class Spoom::Sorbet::Translate::RBSCommentsToSorbetSigs < ::Spoom::Sorbet::Trans | |
|
|
||
| sig { params(node: ::Prism::CallNode).void } | ||
| def visit_attr(node); end | ||
|
|
||
| class << self | ||
| sig { params(source: ::String).returns(T::Boolean) } | ||
| def contains_rbs_syntax?(source); end | ||
|
|
||
| sig { params(ruby_contents: ::String, file: ::String, max_line_length: T.nilable(::Integer)).returns(::String) } | ||
| def rewrite_if_needed(ruby_contents, file:, max_line_length: T.unsafe(nil)); end | ||
| end | ||
| end | ||
|
|
||
| Spoom::Sorbet::Translate::RBSCommentsToSorbetSigs::RBS_ANNOTATION_MARKERS = T.let(T.unsafe(nil), Array) | ||
|
Contributor
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. Let's mark these with |
||
| Spoom::Sorbet::Translate::RBSCommentsToSorbetSigs::RBS_REWRITE_PATTERN = T.let(T.unsafe(nil), Regexp) | ||
|
|
||
| class Spoom::Sorbet::Translate::SorbetAssertionsToRBSComments < ::Spoom::Sorbet::Translate::Translator | ||
| sig do | ||
| params( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -678,11 +678,146 @@ class Range | |
| RB | ||
| end | ||
|
|
||
| def test_contains_rbs_syntax_returns_true_for_supported_rbs_annotations | ||
| [ | ||
| "# @abstract", | ||
| "# @interface", | ||
| "# @sealed", | ||
| "# @final", | ||
| "# @requires_ancestor:", | ||
| "# @override", | ||
| "# @override(allow_incompatible: true)", | ||
| "# @override(allow_incompatible: false)", | ||
| "# @override(allow_incompatible: :visibility)", | ||
| "# @overridable", | ||
| "# @without_runtime", | ||
| ].each do |marker| | ||
| assert( | ||
| RBSCommentsToSorbetSigs.contains_rbs_syntax?(<<~RB), | ||
| # typed: true | ||
|
|
||
| #{marker} | ||
| class Foo; end | ||
| RB | ||
| "#contains_rbs_syntax? should return true for files containing #{marker}", | ||
| ) | ||
| end | ||
| end | ||
|
|
||
| def test_contains_rbs_syntax_returns_true_for_supported_typed_sigils | ||
| [ | ||
| "# typed: ignore", | ||
| "# typed: false", | ||
| "# typed: true", | ||
| "# typed: strict", | ||
| "# typed: strong", | ||
| "# typed: __STDLIB_INTERNAL", | ||
| ].each do |sigil| | ||
| assert( | ||
| RBSCommentsToSorbetSigs.contains_rbs_syntax?(<<~RB), | ||
| #{sigil} | ||
|
|
||
| #: -> void | ||
| def foo; end | ||
| RB | ||
| "#contains_rbs_syntax? should return true for files containing #{sigil}", | ||
| ) | ||
| end | ||
| end | ||
|
|
||
| def test_contains_rbs_syntax_returns_true_when_typed_sigil_is_after_other_magic_comments | ||
| assert(RBSCommentsToSorbetSigs.contains_rbs_syntax?(<<~RB)) | ||
| # frozen_string_literal: true | ||
| # typed: true | ||
|
|
||
| class Foo | ||
| #: -> String | ||
| def foo; end | ||
| end | ||
| RB | ||
|
|
||
| assert(RBSCommentsToSorbetSigs.contains_rbs_syntax?(<<~RB)) | ||
| # frozen_string_literal: true | ||
|
|
||
| # typed: true | ||
|
|
||
| class Foo | ||
| #: -> String | ||
| def foo; end | ||
| end | ||
| RB | ||
| end | ||
|
|
||
| def test_contains_rbs_syntax_returns_true_for_rbs_comments | ||
| assert(RBSCommentsToSorbetSigs.contains_rbs_syntax?(<<~RB)) | ||
| # typed: true | ||
|
|
||
| class Foo | ||
| #: -> String | ||
| def foo; end | ||
| end | ||
| RB | ||
| end | ||
|
|
||
| def test_contains_rbs_syntax_returns_true_for_multiline_rbs_comments | ||
| assert(RBSCommentsToSorbetSigs.contains_rbs_syntax?(<<~RB)) | ||
| # typed: true | ||
|
|
||
| class Foo | ||
| #: -> Array[ | ||
| #| String | ||
| #| ] | ||
| def foo; end | ||
| end | ||
| RB | ||
| end | ||
|
|
||
| def test_contains_rbs_syntax_returns_false_for_files_without_typed_sigil | ||
| refute(RBSCommentsToSorbetSigs.contains_rbs_syntax?(<<~RB)) | ||
| #: -> void | ||
| def foo; end | ||
| RB | ||
| end | ||
|
|
||
| def test_contains_rbs_syntax_returns_false_for_files_without_rbs_syntax | ||
| refute(RBSCommentsToSorbetSigs.contains_rbs_syntax?(<<~RB)) | ||
| # typed: true | ||
|
|
||
| class Foo | ||
| def foo; end | ||
| end | ||
| RB | ||
| end | ||
|
|
||
| def test_contains_rbs_syntax_returns_false_for_unrelated_yard_tags | ||
|
Contributor
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. Good call |
||
| refute(RBSCommentsToSorbetSigs.contains_rbs_syntax?(<<~RB)) | ||
| # typed: true | ||
|
|
||
| # @param value [String] | ||
| # @return [String] | ||
| def foo(value); end | ||
| RB | ||
| end | ||
|
|
||
| def test_rewrite_does_not_call_new_for_files_without_rbs_syntax | ||
| source = <<~RB | ||
| # typed: true | ||
|
|
||
| class Foo | ||
| def foo; end | ||
| end | ||
| RB | ||
|
|
||
| RBSCommentsToSorbetSigs.stub(:new, ->(*) { flunk("should not be called") }) do | ||
| assert_equal(source, RBSCommentsToSorbetSigs.rewrite_if_needed(source, file: "test.rb")) | ||
| end | ||
| end | ||
|
|
||
| private | ||
|
|
||
| #: (String, ?max_line_length: Integer?) -> String | ||
| def rbs_comments_to_sorbet_sigs(ruby_contents, max_line_length: nil) | ||
| Translate.rbs_comments_to_sorbet_sigs(ruby_contents, file: "test.rb", max_line_length: max_line_length) | ||
| RBSCommentsToSorbetSigs.new(ruby_contents, file: "test.rb", max_line_length: max_line_length).rewrite | ||
| end | ||
| end | ||
| end | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will only count changed files in the
Translated signatures in <x> files.message, but I'm happy to drop the commit if we want to leave as isThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it.