Skip to content

Commit c2ae362

Browse files
Earlopainbbatsov
authored andcommitted
Don't parse YAML twice
Once for the duplication check, and once again to convert it to a ruby hash We can instead use the yaml ast from the duplication check and convert that into a hash directly. ``` # Rubocop # Base: $ hyperfine -w 5 -r 20 "bundle exec rubocop Rakefile" Benchmark 1: bundle exec rubocop Rakefile Time (mean ± σ): 1.041 s ± 0.014 s [User: 0.898 s, System: 0.140 s] Range (min … max): 1.013 s … 1.061 s 20 runs # Patch: $ hyperfine -w 5 -r 20 "bundle exec rubocop Rakefile" Benchmark 1: bundle exec rubocop Rakefile Time (mean ± σ): 1.013 s ± 0.008 s [User: 0.865 s, System: 0.146 s] Range (min … max): 0.998 s … 1.027 s 20 runs ``` ~2.7% Faster ``` # Gitlab # Base: $ hyperfine -w 5 -r 20 "bundle exec rubocop Rakefile" Benchmark 1: bundle exec rubocop Rakefile Time (mean ± σ): 3.485 s ± 0.027 s [User: 2.565 s, System: 0.909 s] Range (min … max): 3.445 s … 3.529 s 20 runs # Applied: $ hyperfine -w 5 -r 20 "bundle exec rubocop Rakefile" Benchmark 1: bundle exec rubocop Rakefile Time (mean ± σ): 3.401 s ± 0.029 s [User: 2.483 s, System: 0.906 s] Range (min … max): 3.356 s … 3.466 s 20 runs ``` ~2.5% faster. I expected more since they have so many config files but hey, still something.
1 parent 5f3481f commit c2ae362

File tree

3 files changed

+41
-8
lines changed

3 files changed

+41
-8
lines changed

lib/rubocop/config_loader.rb

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ def load_file(file, check: true)
6767
def load_yaml_configuration(absolute_path)
6868
file_contents = read_file(absolute_path)
6969
yaml_code = Dir.chdir(File.dirname(absolute_path)) { ERB.new(file_contents).result }
70-
check_duplication(yaml_code, absolute_path)
71-
hash = yaml_safe_load(yaml_code, absolute_path) || {}
70+
yaml_tree = check_duplication(yaml_code, absolute_path)
71+
hash = yaml_tree_to_hash(yaml_tree) || {}
7272

7373
puts "configuration from #{absolute_path}" if debug?
7474

@@ -235,8 +235,8 @@ def read_file(absolute_path)
235235
raise ConfigNotFoundError, "Configuration file not found: #{absolute_path}"
236236
end
237237

238-
def yaml_safe_load(yaml_code, filename)
239-
yaml_safe_load!(yaml_code, filename)
238+
def yaml_tree_to_hash(yaml_tree)
239+
yaml_tree_to_hash!(yaml_tree)
240240
rescue ::StandardError
241241
if defined?(::SafeYAML)
242242
raise 'SafeYAML is unmaintained, no longer needed and should be removed'
@@ -245,10 +245,16 @@ def yaml_safe_load(yaml_code, filename)
245245
raise
246246
end
247247

248-
def yaml_safe_load!(yaml_code, filename)
249-
YAML.safe_load(
250-
yaml_code, permitted_classes: [Regexp, Symbol], aliases: true, filename: filename
251-
)
248+
def yaml_tree_to_hash!(yaml_tree)
249+
return nil unless yaml_tree
250+
251+
# Optimization: Because we checked for duplicate keys, we already have the
252+
# yaml tree and don't need to parse it again.
253+
# Also see https://github.com/ruby/psych/blob/v5.1.2/lib/psych.rb#L322-L336
254+
class_loader = YAML::ClassLoader::Restricted.new(%w[Regexp Symbol], [])
255+
scanner = YAML::ScalarScanner.new(class_loader)
256+
visitor = YAML::Visitors::ToRuby.new(scanner, class_loader)
257+
visitor.accept(yaml_tree)
252258
end
253259
end
254260

lib/rubocop/yaml_duplication_checker.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def self.check(yaml_string, filename, &on_duplicated)
1616
return unless tree
1717

1818
traverse(tree, &on_duplicated)
19+
tree
1920
end
2021

2122
def self.traverse(tree, &on_duplicated)

spec/rubocop/config_loader_spec.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,6 +1768,32 @@ def self.inject!
17681768
expect(configuration.to_h).to eq({})
17691769
end
17701770

1771+
it 'rejects non-allowed yaml types' do
1772+
create_file(configuration_path, <<~YAML)
1773+
foo: !ruby/object:Rational
1774+
numerator: 1
1775+
denominator: 2
1776+
YAML
1777+
1778+
expect { load_file }.to raise_error(Psych::DisallowedClass, /Rational/)
1779+
end
1780+
1781+
it 'allows yaml anchors' do
1782+
create_file(configuration_path, <<~YAML)
1783+
Style/Alias: &anchor
1784+
Enabled: false
1785+
Style/Encoding:
1786+
<<: *anchor
1787+
YAML
1788+
1789+
expect(load_file.to_h).to eq(
1790+
{
1791+
'Style/Alias' => { 'Enabled' => false },
1792+
'Style/Encoding' => { 'Enabled' => false }
1793+
}
1794+
)
1795+
end
1796+
17711797
context 'set neither true nor false to value to Enabled' do
17721798
before do
17731799
create_file(configuration_path, <<~YAML)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy