├── .rspec ├── lib ├── gry │ ├── version.rb │ ├── law.rb │ ├── formatter.rb │ ├── option.rb │ ├── rubocop_runner.rb │ ├── congress.rb │ ├── cli.rb │ ├── rubocop_adapter.rb │ └── pilot_study.rb └── gry.rb ├── exe └── gry ├── Gemfile ├── Rakefile ├── .gitignore ├── bin ├── setup └── console ├── gemfiles ├── rubocop_0.49.0.gemfile ├── rubocop_0.61.1.gemfile └── rubocop_head.gemfile ├── .rubocop.yml ├── Appraisals ├── .travis.yml ├── spec ├── gry │ ├── cli_spec.rb │ ├── rubocop_runner_spec.rb │ ├── congress_spec.rb │ ├── rubocop_adapter_spec.rb │ ├── formatter_spec.rb │ └── pilot_study_spec.rb └── spec_helper.rb ├── README.md ├── gry.gemspec ├── Guardfile └── LICENSE /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /lib/gry/version.rb: -------------------------------------------------------------------------------- 1 | module Gry 2 | VERSION = "0.7.0" 3 | end 4 | -------------------------------------------------------------------------------- /lib/gry/law.rb: -------------------------------------------------------------------------------- 1 | module Gry 2 | Law = Struct.new(:name, :bill, :letter) 3 | end 4 | -------------------------------------------------------------------------------- /exe/gry: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'gry' 4 | 5 | Gry::CLI.new(ARGV).run(STDOUT) 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in gry.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new("spec") 5 | task :default => :spec 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | /gemfiles/*.lock 11 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /gemfiles/rubocop_0.49.0.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rubocop", "0.49.0" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rubocop_0.61.1.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rubocop", "0.61.1" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rubocop_head.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rubocop", github: "rubocop-hq/rubocop" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.4 3 | DisabledByDefault: true 4 | 5 | Lint: 6 | Enabled: true 7 | 8 | Lint/AmbiguousBlockAssociation: 9 | Exclude: 10 | - spec/**/*_spec.rb 11 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise "rubocop-head" do 2 | gem "rubocop", github: 'rubocop-hq/rubocop' 3 | end 4 | 5 | appraise "rubocop-0.49.0" do 6 | gem "rubocop", "0.49.0" 7 | end 8 | 9 | appraise "rubocop-0.61.1" do 10 | gem "rubocop", "0.61.1" 11 | end 12 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "gry" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | cache: bundler 3 | rvm: 4 | - 2.4.5 5 | - 2.5.3 6 | - 2.6.0 7 | - ruby-head 8 | gemfile: 9 | - gemfiles/rubocop_0.49.0.gemfile # The oldest version in the supported versions 10 | - gemfiles/rubocop_0.61.1.gemfile # The newest version in the supported versions 11 | - gemfiles/rubocop_head.gemfile 12 | matrix: 13 | include: 14 | allow_failures: 15 | - gemfile: gemfiles/rubocop_head.gemfile 16 | fast_finish: true 17 | -------------------------------------------------------------------------------- /lib/gry.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'tempfile' 3 | require 'yaml' 4 | require 'json' 5 | require 'optparse' 6 | require 'open3' 7 | require 'pathname' 8 | require 'digest/sha1' 9 | 10 | require 'rubocop' 11 | require 'parallel' 12 | 13 | require "gry/version" 14 | require 'gry/rubocop_runner' 15 | require 'gry/congress' 16 | require 'gry/law' 17 | require 'gry/pilot_study' 18 | require "gry/option" 19 | require 'gry/cli' 20 | require 'gry/rubocop_adapter' 21 | require 'gry/formatter' 22 | 23 | module Gry 24 | @debug = false 25 | def self.debug? 26 | @debug 27 | end 28 | 29 | def self.debug_mode! 30 | @debug = true 31 | end 32 | 33 | @mu_debug_print = Thread::Mutex.new 34 | def self.debug_log(msg) 35 | @mu_debug_print.lock 36 | 37 | return unless debug? 38 | message = msg.is_a?(String) ? msg : msg.inspect 39 | $stderr.puts message 40 | ensure 41 | @mu_debug_print.unlock 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/gry/cli_spec.rb: -------------------------------------------------------------------------------- 1 | require 'stringio' 2 | 3 | describe Gry::CLI do 4 | describe '#run' do 5 | include_context :chdir 6 | 7 | let(:cli){Gry::CLI.new(args)} 8 | let(:out){StringIO.new} 9 | subject(:run){cli.run(out)} 10 | 11 | shared_examples 'writes yaml' do 12 | it 'write a yaml' do 13 | run 14 | result = out.string 15 | is_asserted_by{ !result.empty? } 16 | is_asserted_by{ YAML.load(result).is_a? Hash } 17 | end 18 | end 19 | 20 | unless ENV['DONT_RUN_SLOW_SPEC'] 21 | context 'with --fast' do 22 | let(:args){%w[--fast]} 23 | include_examples 'writes yaml' 24 | end 25 | 26 | context 'with --no-fast' do 27 | let(:args){%w[--no-fast]} 28 | include_examples 'writes yaml' 29 | end 30 | 31 | context 'without args' do 32 | let(:args){%w[]} 33 | include_examples 'writes yaml' 34 | end 35 | 36 | context 'when a cop name is specified' do 37 | let(:args){%w[Style/AndOr]} 38 | include_examples 'writes yaml' 39 | end 40 | end 41 | 42 | context 'with --version' do 43 | let(:args){%w[--version]} 44 | 45 | it 'writes a version' do 46 | run 47 | result = out.string 48 | is_asserted_by{ !result.empty? } 49 | is_asserted_by{ result.start_with?("gry #{Gry::VERSION}") } 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gry 2 | 3 | Gry Generates a .Rubocop.Yml automatically. 4 | 5 | [![Build Status](https://travis-ci.org/pocke/gry.svg?branch=master)](https://travis-ci.org/pocke/gry) 6 | [![Coverage Status](https://coveralls.io/repos/github/pocke/gry/badge.svg?branch=master)](https://coveralls.io/github/pocke/gry?branch=master) 7 | [![Gem Version](https://badge.fury.io/rb/gry.svg)](https://badge.fury.io/rb/gry) 8 | 9 | Gry extracts coding style guide as a `.rubocop.yml` from your code that already exists. 10 | 11 | 12 | ## Installation 13 | 14 | ```sh 15 | $ gem install gry 16 | ``` 17 | 18 | ## Usage 19 | 20 | ```sh 21 | # Generate a .rubocop.yml for all configurable cops. 22 | $ gry >> .rubocop.yml 23 | 24 | # Generate a .rubocop.yml for specified cops only. 25 | $ gry Style/AndOr Style/VariableName >> .rubocop.yml 26 | ``` 27 | 28 | 29 | License 30 | ------- 31 | 32 | 33 | Copyright 2017 Masataka Pocke Kuwabara 34 | 35 | Licensed under the Apache License, Version 2.0 (the "License"); 36 | you may not use this file except in compliance with the License. 37 | You may obtain a copy of the License at 38 | 39 | http://www.apache.org/licenses/LICENSE-2.0 40 | 41 | Unless required by applicable law or agreed to in writing, software 42 | distributed under the License is distributed on an "AS IS" BASIS, 43 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 44 | See the License for the specific language governing permissions and 45 | limitations under the License. 46 | -------------------------------------------------------------------------------- /lib/gry/formatter.rb: -------------------------------------------------------------------------------- 1 | module Gry 2 | class Formatter 3 | def initialize(display_disabled_cops:) 4 | @display_disabled_cops = display_disabled_cops 5 | end 6 | 7 | # @param gry_result [Array] 8 | # @return [String] a yaml string 9 | def format(laws) 10 | confs = laws.map do |law| 11 | if law.letter 12 | letter = {law.name => law.letter} 13 | else 14 | if @display_disabled_cops 15 | letter = {law.name => {'Enabled' => false}} 16 | else 17 | next 18 | end 19 | end 20 | comment = RubocopAdapter.metrics_cop?(law.name) ? 21 | '' : 22 | to_comment(law.bill) 23 | yaml = to_yaml(letter) 24 | comment + yaml 25 | end.compact 26 | 27 | to_yaml(RubocopAdapter.config_base) + 28 | "\n" + 29 | confs.join("\n") 30 | end 31 | 32 | 33 | private 34 | 35 | def to_yaml(hash) 36 | YAML.dump(hash)[4..-1] 37 | end 38 | 39 | def to_comment(set_count) 40 | set_count.map do |setting, offenses| 41 | count = offenses.size 42 | 43 | x = setting 44 | .reject{|key, _| key == 'Enabled'} 45 | .map{|key, value| "#{key}: #{value}"} 46 | .join(', ') 47 | "# #{x} => #{count} #{offenses(count)}\n" 48 | end.join 49 | end 50 | 51 | def offenses(count) 52 | if count > 1 53 | 'offenses' 54 | else 55 | 'offense' 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/gry/option.rb: -------------------------------------------------------------------------------- 1 | module Gry 2 | class Option 3 | attr_reader :args, :process, :version, :fast, :max_count, :min_difference, :display_disabled_cops, :metrics_percentile, :cache, :raw 4 | 5 | def initialize(argv) 6 | opt = OptionParser.new 7 | @version = false 8 | @process = Parallel.processor_count 9 | @fast = true 10 | @max_count = 10 11 | @min_difference = 10 12 | @display_disabled_cops = false 13 | @metrics_percentile = 95 14 | @cache = true 15 | @raw = false 16 | 17 | opt.banner = 'Usage: gry [options] [Cop1, Cop2, ...]' 18 | 19 | opt.on('-d', '--debug', 'Output debug log.') {Gry.debug_mode!} 20 | opt.on('-p', '--process=VAL', 'Number of parallel processes.') {|v| @process = v.to_i} 21 | opt.on('-v', '--version', 'Display version.') {@version = true} 22 | opt.on('--[no-]fast', 'Run only fast cops. Default: true') {|v| @fast = v} 23 | opt.on('--max-count=10', 'Upper limit of issues.') {|v| @max_count = v.to_i} 24 | opt.on('--min-difference=10', 'Lower limit of issues number difference') {|v| @min_difference = v.to_i} 25 | opt.on('--metrics-percentile=95', 'Percentile for allowed complex code') {|v| @metrics_percentile = v.to_i} 26 | opt.on('--display-disabled-cops', 'Display disabled cops') {|v| @display_disabled_cops = v} 27 | opt.on('--[no-]cache', 'Run gry with cache. Default: true') {|v| @cache = v} 28 | opt.on('--raw', 'Display raw data(JSON). Default: false') {|v| @raw = v} 29 | 30 | @args = opt.parse(argv) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/gry/rubocop_runner_spec.rb: -------------------------------------------------------------------------------- 1 | describe Gry::RubocopRunner do 2 | describe '.new' do 3 | it 'sets @setting' do 4 | setting_arg = { 5 | 'Style/AndOr' => { 6 | 'EnforcedStyle' => 'always', 7 | 'Enabled' => true, 8 | } 9 | } 10 | 11 | runner = Gry::RubocopRunner.new(['Style/AndOr'], setting_arg) 12 | setting = runner.instance_variable_get(:@setting) 13 | expect(setting['AllCops']['TargetRubyVersion']).to eq Gry::RubocopAdapter.target_ruby_version 14 | expect(setting['Style/AndOr']).to eq setting_arg['Style/AndOr'] 15 | end 16 | end 17 | 18 | describe '#parse_stderr' do 19 | let(:stderr){<<-END} 20 | An error occurred while Style/AndOr cop was inspecting /home/pocke/ghq/github.com/bbatsov/rubocop/spec/rubocop/node_pattern_spec.rb. 21 | To see the complete backtrace run rubocop -d. 22 | An error occurred while Rails/RequestReferer cop was inspecting /home/pocke/ghq/github.com/bbatsov/rubocop/spec/rubocop/node_pattern_spec.rb. 23 | To see the complete backtrace run rubocop -d. 24 | An error occurred while Style/AndOr cop was inspecting /home/pocke/ghq/github.com/bbatsov/rubocop/spec/rubocop/node_pattern_spec.rb. 25 | To see the complete backtrace run rubocop -d. 26 | END 27 | 28 | let(:setting_arg){{ 29 | 'Style/AndOr' => { 30 | 'EnforcedStyle' => 'always', 31 | 'Enabled' => true, 32 | } 33 | }} 34 | 35 | subject{Gry::RubocopRunner.new(['Style/AndOr'], setting_arg).__send__(:parse_stderr, stderr)} 36 | 37 | it {is_expected.to eq %w[Style/AndOr Rails/RequestReferer]} 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/gry/rubocop_runner.rb: -------------------------------------------------------------------------------- 1 | module Gry 2 | # Run RuboCop with specific cops and config 3 | class RubocopRunner 4 | # @param cops [Array] cop names. e.g.) ['Style/EmptyElse'] 5 | # @param setting [Hash] e.g.) {'Style/EmptyElse' => {'EnforcedStyle' => 'both'}} 6 | def initialize(cops, setting) 7 | @cops = cops 8 | setting_base = RubocopAdapter.config_base 9 | @setting = setting_base.merge(setting) 10 | @tmp_setting_path = nil 11 | end 12 | 13 | def run 14 | prepare 15 | stdout, stderr = run_rubocop 16 | crashed_cops = parse_stderr(stderr) 17 | Gry.debug_log "Crashed cops: #{crashed_cops}" 18 | [JSON.parse(stdout), crashed_cops] 19 | ensure 20 | clean 21 | end 22 | 23 | 24 | private 25 | 26 | def prepare 27 | f = Tempfile.create(['gry-rubocop-config-', '.yml']) 28 | @tmp_setting_path = f.path 29 | 30 | f.write(YAML.dump(@setting)) 31 | f.close 32 | end 33 | 34 | def run_rubocop 35 | cmd = %W[ 36 | rubocop 37 | --only #{@cops.join(',')} 38 | --config #{@tmp_setting_path} 39 | --format json 40 | ] 41 | Gry.debug_log "Execute: #{cmd.join(' ')}" 42 | stdout, stderr, _status = *Open3.capture3(*cmd) 43 | [stdout, stderr] 44 | end 45 | 46 | def clean 47 | FileUtils.rm(@tmp_setting_path) if @tmp_setting_path && !Gry.debug? 48 | end 49 | 50 | # @param stderr [String] stderr output of RuboCop 51 | # @return [Array] crashed cop list 52 | def parse_stderr(stderr) 53 | stderr 54 | .scan(%r!An error occurred while ([\w/]+) cop was inspecting!) 55 | .flatten 56 | .uniq 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /gry.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'gry/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "gry" 8 | spec.version = Gry::VERSION 9 | spec.authors = ["Masataka Kuwabara"] 10 | spec.email = ["p.ck.t22@gmail.com"] 11 | 12 | spec.summary = %q{Gry generates `.rubocop.yml` automatically from your source code.} 13 | spec.description = %q{Gry generates `.rubocop.yml` automatically from your source code.} 14 | spec.homepage = "https://github.com/pocke/gry" 15 | 16 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 17 | spec.bindir = "exe" 18 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 19 | spec.require_paths = ["lib"] 20 | spec.licenses = ['Apache-2.0'] 21 | 22 | spec.required_ruby_version = '>= 2.4.0' 23 | 24 | spec.add_development_dependency "bundler", "< 3", "> 1" 25 | spec.add_development_dependency "rake", "> 10" 26 | spec.add_development_dependency "rspec", "~> 3.5.0" 27 | spec.add_development_dependency "rspec-power_assert", "~> 0.4.0" 28 | spec.add_development_dependency 'coveralls', '~> 0.8.16' 29 | spec.add_development_dependency 'simplecov', '~> 0.12.0' 30 | spec.add_development_dependency "guard", "~> 2.14.0" 31 | spec.add_development_dependency "guard-rspec", "~> 4.7.3" 32 | spec.add_development_dependency "guard-bundler" 33 | spec.add_development_dependency "guard-rubocop", "~> 1.2.0" 34 | spec.add_development_dependency "rubocop", ">= 0.61.1" 35 | spec.add_development_dependency "appraisal" 36 | 37 | spec.add_runtime_dependency "rubocop", ">= 0.49.0" 38 | spec.add_runtime_dependency "parallel", "~> 1.10" 39 | end 40 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | ## Uncomment and set this to only include directories you want to watch 5 | # directories %w(app lib config test spec features) \ 6 | # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")} 7 | 8 | ## Note: if you are using the `directories` clause above and you are not 9 | ## watching the project directory ('.'), then you will want to move 10 | ## the Guardfile to a watched dir and symlink it back, e.g. 11 | # 12 | # $ mkdir config 13 | # $ mv Guardfile config/ 14 | # $ ln -s config/Guardfile . 15 | # 16 | # and, you'll have to watch "config/Guardfile" instead of "Guardfile" 17 | 18 | guard :bundler do 19 | require 'guard/bundler' 20 | require 'guard/bundler/verify' 21 | helper = Guard::Bundler::Verify.new 22 | 23 | files = ['Gemfile'] 24 | files += Dir['*.gemspec'] if files.any? { |f| helper.uses_gemspec?(f) } 25 | 26 | # Assume files are symlinked from somewhere 27 | files.each { |file| watch(helper.real_path(file)) } 28 | end 29 | 30 | # Note: The cmd option is now required due to the increasing number of ways 31 | # rspec may be run, below are examples of the most common uses. 32 | # * bundler: 'bundle exec rspec' 33 | # * bundler binstubs: 'bin/rspec' 34 | # * spring: 'bin/rspec' (This will use spring if running and you have 35 | # installed the spring binstubs per the docs) 36 | # * zeus: 'zeus rspec' (requires the server to be started separately) 37 | # * 'just' rspec: 'rspec' 38 | 39 | guard :rspec, cmd: "DONT_RUN_SLOW_SPEC=1 bundle exec rspec" do 40 | require "guard/rspec/dsl" 41 | dsl = Guard::RSpec::Dsl.new(self) 42 | 43 | # Feel free to open issues for suggestions and improvements 44 | 45 | # RSpec files 46 | rspec = dsl.rspec 47 | watch(rspec.spec_helper) { rspec.spec_dir } 48 | watch(rspec.spec_support) { rspec.spec_dir } 49 | watch(rspec.spec_files) 50 | 51 | # Ruby files 52 | ruby = dsl.ruby 53 | dsl.watch_spec_files_for(ruby.lib_files) 54 | end 55 | 56 | guard :rubocop do 57 | watch(%r{.+\.rb$}) 58 | watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) } 59 | end 60 | -------------------------------------------------------------------------------- /lib/gry/congress.rb: -------------------------------------------------------------------------------- 1 | module Gry 2 | class Congress 3 | def initialize(max_count:, min_difference:, metrics_percentile:) 4 | @max_count = max_count 5 | @min_difference = min_difference 6 | @metrics_percentile = metrics_percentile 7 | end 8 | 9 | # @param name [String] cop name 10 | # @param bill [Hash{Hash => Array}] rubocop results 11 | # { 12 | # {config1} => [offenses], 13 | # {config2} => [offenses], 14 | # {config3} => [offenses], 15 | # } 16 | # @return [Law] 17 | def discuss(name, bill) 18 | letter = letter(name, bill) 19 | Law.new(name, bill, letter) 20 | end 21 | 22 | 23 | private 24 | 25 | def letter(name, bill) 26 | if RubocopAdapter.metrics_cop?(name) 27 | letter_for_metrics(name, bill) 28 | else 29 | letter_for_enforced_style(bill) 30 | end 31 | end 32 | 33 | def letter_for_metrics(name, bill) 34 | raise "bill.size is not 1, got #{bill.size}" unless bill.size == 1 35 | values = bill 36 | .values[0] 37 | .map{|offense| offense['message']} 38 | .map{|message| message[%r!\[([0-9.]+)/[0-9.]+\]$!, 1]} 39 | .map(&:to_i) 40 | .sort 41 | 42 | max = percentile(values) 43 | if name == 'Metrics/AbcSize' 44 | max = max.round(2) 45 | else 46 | max = max.round 47 | end 48 | 49 | { 50 | 'Enabled' => true, 51 | 'Max' => max, 52 | } 53 | end 54 | 55 | def letter_for_enforced_style(bill) 56 | # [[conf, offenses], ...] 57 | sorted = bill.sort_by{|_conf, offenses| offenses.size} 58 | min_count = sorted[0].last.size 59 | return nil if min_count > @max_count 60 | 61 | second_count = sorted[1].last.size 62 | return nil if second_count - min_count < @min_difference 63 | 64 | sorted.first.first 65 | end 66 | 67 | # @param values [Array] 68 | # @return [Numeric] 69 | def percentile(values) 70 | size = values.size 71 | index = size.to_f / 100 * @metrics_percentile - 1 72 | v1 = values[index.floor] 73 | v2 = values[index.ceil] 74 | 75 | v1 + (index - index.ceil) * (v2 - v1) 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/gry/cli.rb: -------------------------------------------------------------------------------- 1 | module Gry 2 | class CLI 3 | CACHE_DIR = Pathname(ENV['XDG_CACHE_HOME'] || '~/.cache').expand_path / 'gry' 4 | 5 | def initialize(argv) 6 | @argv = argv 7 | end 8 | 9 | def run(writer) 10 | opt = Option.new(@argv) 11 | if opt.version 12 | rubocop_version, = *Open3.capture3('rubocop', '--verbose-version') 13 | writer.puts "gry #{VERSION} (RuboCop #{rubocop_version.chomp})" 14 | return 15 | end 16 | 17 | cops = opt.args.empty? ? RubocopAdapter.configurable_cops : opt.args 18 | if opt.fast 19 | cops.reject!{|cop| cop == 'Layout/AlignHash'} 20 | end 21 | 22 | bills = (opt.cache && restore_cache(cops)) || begin 23 | pilot_study = Gry::PilotStudy.new(cops, process: opt.process) 24 | pilot_study.analyze.tap do |b| 25 | save_cache(b, cops) 26 | end 27 | end 28 | if opt.raw 29 | writer.puts JSON.generate(bills) 30 | return 31 | end 32 | 33 | congress = Congress.new( 34 | max_count: opt.max_count, 35 | min_difference: opt.min_difference, 36 | metrics_percentile: opt.metrics_percentile, 37 | ) 38 | laws = bills.map do |cop_name, bill| 39 | congress.discuss(cop_name, bill) 40 | end 41 | 42 | fmt = Formatter.new(display_disabled_cops: opt.display_disabled_cops) 43 | writer.puts fmt.format(laws) 44 | end 45 | 46 | private 47 | 48 | def save_cache(bills, cops) 49 | Dir.mkdir(CACHE_DIR) unless CACHE_DIR.exist? 50 | cache = { 51 | bills: bills, 52 | cops: cops, 53 | created_at: Time.now, 54 | } 55 | File.write(cache_path, Marshal.dump(cache)) 56 | end 57 | 58 | def restore_cache(cops) 59 | return unless cache_path.exist? 60 | cache = Marshal.load(File.read(cache_path)) 61 | 62 | return unless cops == cache[:cops] 63 | 64 | cached_time = cache[:created_at] 65 | not_changed = RubocopAdapter.find_target_files.map(&:chomp).all?{|file| File.ctime(file) < cached_time} 66 | return unless not_changed 67 | 68 | return cache[:bills] 69 | end 70 | 71 | def cache_path 72 | CACHE_DIR / Digest::SHA1.hexdigest(Dir.pwd) 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/gry/rubocop_adapter.rb: -------------------------------------------------------------------------------- 1 | module Gry 2 | module RubocopAdapter 3 | module_function 4 | 5 | def default_config 6 | RuboCop::ConfigLoader.default_configuration 7 | end 8 | 9 | def configurable_cops 10 | conf = RuboCop::ConfigLoader.default_configuration.to_h 11 | conf 12 | .select{|key, cop_conf| !enforced_styles(cop_conf).empty? || metrics_cop?(key) } 13 | .reject{|key, _cop_conf| !rails? && key.start_with?('Rails/')} 14 | .select{|key, _cop_conf| conf = config_specified_by_user.for_cop(key); conf.empty? || conf.keys == ['Enabled']} # Ignore always configured cops 15 | .keys 16 | end 17 | 18 | # @param cop_conf [Hash] 19 | def enforced_styles(cop_conf) 20 | cop_conf.keys.select do |key| 21 | key.start_with?('Enforced') 22 | end 23 | end 24 | 25 | # @param cop_name [String] 26 | # @return [Boolean] 27 | def metrics_cop?(cop_name) 28 | return false unless cop_name.start_with?('Metrics') 29 | # https://github.com/bbatsov/rubocop/pull/4055 30 | exclude_cops = Gem::Version.new(RuboCop::Version.version) >= Gem::Version.new('0.48.0') ? 31 | %w[Metrics/BlockNesting] : 32 | %w[Metrics/ParameterLists Metrics/BlockNesting] 33 | !exclude_cops.include?(cop_name) 34 | end 35 | 36 | def to_supported_styles(enforced_style) 37 | RuboCop::Cop::Util.to_supported_styles(enforced_style) 38 | end 39 | 40 | def target_ruby_version 41 | current_config.target_ruby_version 42 | end 43 | 44 | def rails? 45 | return @rails if defined?(@rails) 46 | @rails = current_config.to_h.dig('Rails', 'Enabled') || 47 | File.exist?('./bin/rails') || 48 | File.exist?('./script/rails') 49 | end 50 | 51 | def current_config 52 | RuboCop::ConfigStore.new.for(Dir.pwd) 53 | end 54 | 55 | def config_specified_by_user 56 | path = RuboCop::ConfigLoader.configuration_file_for(Dir.pwd) 57 | if path == RuboCop::ConfigLoader::DEFAULT_FILE 58 | RuboCop::Config.new 59 | else 60 | RuboCop::ConfigLoader.load_file(path) 61 | end 62 | end 63 | 64 | def config_base 65 | base = { 66 | 'AllCops' => { 67 | 'TargetRubyVersion' => RubocopAdapter.target_ruby_version, 68 | }, 69 | } 70 | return base unless rails? 71 | base.merge({ 72 | 'Rails' => { 73 | 'Enabled' => true, 74 | } 75 | }) 76 | end 77 | 78 | def find_target_files 79 | `rubocop --list-target-files`.each_line 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/gry/pilot_study.rb: -------------------------------------------------------------------------------- 1 | module Gry 2 | class PilotStudy 3 | # @param cops [Array] cop names. e.g.) ['Style/EmptyElse'] 4 | def initialize(cops, process:) 5 | @cops = cops 6 | @process = process == 1 ? 0 : process 7 | end 8 | 9 | def analyze 10 | configs = @cops.map{|cop_name| cop_configs(cop_name)} 11 | max = configs.max_by(&:size).size 12 | configs.each do |c| 13 | c.fill(nil, c.size..(max-1)) 14 | end 15 | 16 | rubocop_args = configs.transpose.map do |conf_set| 17 | compacted = conf_set.compact 18 | setting = compacted.inject({}) do |a, b| 19 | a.merge(b) 20 | end 21 | cops = setting.keys 22 | 23 | [cops, setting] 24 | end 25 | 26 | execute_rubocop(rubocop_args) 27 | end 28 | 29 | 30 | private 31 | 32 | # @param rubocop_args [Array] 33 | # ['Style/DotPosition', {...}, ...] 34 | # @param result [Hash] 35 | # {'Style/DotPosition' => { {conf} => count}} 36 | def execute_rubocop(rubocop_args) 37 | res = {} 38 | 39 | runners = rubocop_args.map do |arg| 40 | cops, setting = *arg 41 | setting.each do |cop_name, s| 42 | res[cop_name] ||= {} 43 | res[cop_name][s] ||= [] 44 | end 45 | 46 | RubocopRunner.new(cops, setting) 47 | end 48 | 49 | results = Parallel.map(runners, in_threads: @process, &:run) 50 | 51 | crashed_cops = [] 52 | results.each.with_index do |(result, crashed_cops_in_this_step), idx| 53 | crashed_cops.concat(crashed_cops_in_this_step) 54 | 55 | setting = rubocop_args[idx][1] 56 | 57 | result['files'].each do |f| 58 | f['offenses'].each do |offense| 59 | cop_name = offense['cop_name'] 60 | next if cop_name == 'Syntax' # Syntax cop is not configurable. 61 | res[cop_name][setting[cop_name]].push(offense) 62 | end 63 | end 64 | end 65 | 66 | crashed_cops.each do |cop_name| 67 | res.delete(cop_name) 68 | end 69 | 70 | res 71 | end 72 | 73 | # @param cop_name [String] 74 | # @return [Array] 75 | def cop_configs(cop_name) 76 | if RubocopAdapter.metrics_cop?(cop_name) 77 | cop_configs_for_metrics(cop_name) 78 | else 79 | cop_configs_for_enforced_style(cop_name) 80 | end 81 | end 82 | 83 | # @param cop_name [String] 84 | # @return [Array] 85 | def cop_configs_for_metrics(cop_name) 86 | [ 87 | { 88 | cop_name => { 89 | 'Enabled' => true, 90 | 'Max' => 0, 91 | } 92 | } 93 | ] 94 | end 95 | 96 | # @param cop_name [String] 97 | # @return [Array] 98 | def cop_configs_for_enforced_style(cop_name) 99 | cop_config = RubocopAdapter.default_config[cop_name] 100 | 101 | # e.g. %w[EnforcedHashRocketStyle EnforcedColonStyle EnforcedLastArgumentHashStyle] 102 | enforced_style_names = RubocopAdapter.enforced_styles(cop_config) 103 | 104 | # e.g. [ 105 | # %w[key separator table], 106 | # %w[key separator table], 107 | # w[always_inspect always_ignore ignore_implicit ignore_explicit], 108 | # ] 109 | supported_styles = enforced_style_names 110 | .map{|style_name| RubocopAdapter.to_supported_styles(style_name)} 111 | .map{|supported_style_name| cop_config[supported_style_name]} 112 | 113 | supported_styles[0].product(*supported_styles[1..-1]).map do |style_values| 114 | conf = style_values 115 | .map.with_index{|value, idx| [enforced_style_names[idx], value]} 116 | .to_h 117 | conf['Enabled'] = true 118 | { 119 | cop_name => conf 120 | } 121 | end 122 | end 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /spec/gry/congress_spec.rb: -------------------------------------------------------------------------------- 1 | describe Gry::Congress do 2 | describe '#discuss' do 3 | subject(:discuss) do 4 | Gry::Congress.new( 5 | max_count: max_count, 6 | min_difference: min_difference, 7 | metrics_percentile: metrics_percentile, 8 | ).discuss(name, bill) 9 | end 10 | let(:max_count){10} 11 | let(:min_difference){10} 12 | let(:metrics_percentile){95} 13 | 14 | def offenses(count) 15 | [{'message' => 'hoge'}] * count 16 | end 17 | 18 | context 'when accepts' do 19 | let(:bill) do 20 | { 21 | {'EnforcedStyle' => 'foo'} => offenses(17), 22 | {'EnforcedStyle' => 'bar'} => offenses(6), 23 | {'EnforcedStyle' => 'baz'} => offenses(20), 24 | } 25 | end 26 | let(:name){'Style/FooBar'} 27 | 28 | it 'returns a law' do 29 | law = discuss 30 | is_asserted_by{ law.is_a? Gry::Law } 31 | is_asserted_by{ law.name == name } 32 | is_asserted_by{ law.letter == {'EnforcedStyle' => 'bar'} } 33 | end 34 | end 35 | 36 | 37 | context 'with boundary value' do 38 | let(:bill) do 39 | { 40 | {'EnforcedStyle' => 'foo'} => offenses(30), 41 | {'EnforcedStyle' => 'bar'} => offenses(20), 42 | {'EnforcedStyle' => 'baz'} => offenses(10), 43 | } 44 | end 45 | let(:name){'Style/FooBar'} 46 | 47 | it 'returns a law' do 48 | law = discuss 49 | is_asserted_by{ law.is_a? Gry::Law } 50 | is_asserted_by{ law.name == name } 51 | is_asserted_by{ law.letter == {'EnforcedStyle' => 'baz'} } 52 | end 53 | end 54 | 55 | context 'when bill is rejected by max_count' do 56 | let(:bill) do 57 | { 58 | {'EnforcedStyle' => 'foo'} => offenses(30), 59 | {'EnforcedStyle' => 'bar'} => offenses(20), 60 | {'EnforcedStyle' => 'baz'} => offenses(10), 61 | } 62 | end 63 | let(:name){'Style/FooBar'} 64 | let(:max_count){9} 65 | 66 | it 'returns a law' do 67 | law = discuss 68 | is_asserted_by{ law.is_a? Gry::Law } 69 | is_asserted_by{ law.name == name } 70 | is_asserted_by{ law.letter.nil? } 71 | end 72 | end 73 | 74 | context 'when bill is rejected by min_difference' do 75 | let(:bill) do 76 | { 77 | {'EnforcedStyle' => 'foo'} => offenses(30), 78 | {'EnforcedStyle' => 'bar'} => offenses(20), 79 | {'EnforcedStyle' => 'baz'} => offenses(10), 80 | } 81 | end 82 | let(:name){'Style/FooBar'} 83 | let(:min_difference){20} 84 | 85 | it 'returns a law' do 86 | law = discuss 87 | is_asserted_by{ law.is_a? Gry::Law } 88 | is_asserted_by{ law.name == name } 89 | is_asserted_by{ law.letter.nil? } 90 | end 91 | end 92 | 93 | context 'when metrics cop' do 94 | let(:bill) do 95 | { 96 | {'Max' => 0} => [ 97 | # 20 * 30, 30 * 30, 80 * 30, 101 ~ 110 98 | [{'message' => 'Line is too long. [20/0]'}] * 30, 99 | [{'message' => 'Line is too long. [30/0]'}] * 30, 100 | [{'message' => 'Line is too long. [80/0]'}] * 30, 101 | {'message' => 'Line is too long. [101/0]'}, 102 | {'message' => 'Line is too long. [102/0]'}, 103 | {'message' => 'Line is too long. [103/0]'}, 104 | {'message' => 'Line is too long. [104/0]'}, 105 | {'message' => 'Line is too long. [105/0]'}, 106 | {'message' => 'Line is too long. [106/0]'}, 107 | {'message' => 'Line is too long. [107/0]'}, 108 | {'message' => 'Line is too long. [108/0]'}, 109 | {'message' => 'Line is too long. [109/0]'}, 110 | {'message' => 'Line is too long. [110/0]'}, 111 | ].flatten.shuffle, 112 | } 113 | end 114 | let(:name){'Metrics/LineLength'} 115 | 116 | it 'returns a law with 95 percentile max' do 117 | law = discuss 118 | is_asserted_by{ law.is_a? Gry::Law } 119 | is_asserted_by{ law.name == name } 120 | is_asserted_by{ law.letter == {'Enabled' => true, 'Max' => 105} } 121 | end 122 | end 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /spec/gry/rubocop_adapter_spec.rb: -------------------------------------------------------------------------------- 1 | describe Gry::RubocopAdapter do 2 | describe '.default_config' do 3 | it 'returns a RuboCop::Config' do 4 | expect(Gry::RubocopAdapter.default_config).to be_a RuboCop::Config 5 | end 6 | end 7 | 8 | describe '.configurable_cops' do 9 | it 'returns a Array of String' do 10 | cops = Gry::RubocopAdapter.configurable_cops 11 | expect(cops).to be_a Array 12 | expect(cops).not_to be_empty 13 | expect(cops).to be_all{|cop| cop.is_a? String} 14 | expect(cops).to be_include 'Style/Alias' 15 | # expect(cops).to be_include 'Metrics/AbcSize' 16 | expect(cops).not_to be_include 'Rails/NotNullColumn' 17 | end 18 | 19 | context 'when rails' do 20 | it 'returns rails cops' do 21 | allow(Gry::RubocopAdapter).to receive(:rails?).and_return(true) 22 | 23 | cops = Gry::RubocopAdapter.configurable_cops 24 | expect(cops).to include 'Rails/RequestReferer' 25 | end 26 | end 27 | 28 | context 'when not rails' do 29 | it 'returns rails cops' do 30 | allow(Gry::RubocopAdapter).to receive(:rails?).and_return(false) 31 | 32 | cops = Gry::RubocopAdapter.configurable_cops 33 | expect(cops).to be_none{|cop| cop.start_with?('Rails')} 34 | end 35 | end 36 | end 37 | 38 | describe '.enforced_styles' do 39 | shared_examples 'returns_enforced_styles' do |cop_name, expected| 40 | it "returns configurable styles for #{cop_name}" do 41 | cop_conf = Gry::RubocopAdapter.default_config[cop_name] 42 | styles = Gry::RubocopAdapter.enforced_styles(cop_conf) 43 | expect(styles).to eq expected 44 | end 45 | end 46 | 47 | include_examples 'returns_enforced_styles', 'Style/AndOr', %w[EnforcedStyle] 48 | include_examples 'returns_enforced_styles', 'Style/NumericLiteralPrefix', %w[EnforcedOctalStyle] 49 | include_examples 'returns_enforced_styles', 'Layout/AlignHash', %w[ 50 | EnforcedHashRocketStyle 51 | EnforcedColonStyle 52 | EnforcedLastArgumentHashStyle 53 | ] 54 | include_examples 'returns_enforced_styles', 'Rails/NotNullColumn', %w[] 55 | end 56 | 57 | describe '.metrics_cop?' do 58 | shared_examples :check do |cop_name, expected| 59 | it "returns configurable styles for #{cop_name}" do 60 | res = Gry::RubocopAdapter.metrics_cop?(cop_name) 61 | expect(res).to eq expected 62 | end 63 | end 64 | 65 | include_examples :check, 'Style/AndOr', false 66 | include_examples :check, 'Metrics/AbcSize', true 67 | include_examples :check, 'Performance/RedundantMerge', false 68 | end 69 | 70 | describe '.to_supported_styles' do 71 | it 'returns a supported style name' do 72 | res = Gry::RubocopAdapter.to_supported_styles('EnforcedStyle') 73 | expect(res).to eq 'SupportedStyles' 74 | end 75 | end 76 | 77 | describe '.target_ruby_version' do 78 | it 'returns a version' do 79 | res = Gry::RubocopAdapter.target_ruby_version 80 | expect(res).to be_a Float 81 | end 82 | end 83 | 84 | describe '.config_base' do 85 | context 'when a rails project' do 86 | before do 87 | allow(Gry::RubocopAdapter).to receive(:rails?).and_return(true) 88 | end 89 | 90 | it 'returns a Hash' do 91 | base = Gry::RubocopAdapter.config_base 92 | expect(base).to match({ 93 | 'AllCops' => { 94 | 'TargetRubyVersion' => kind_of(Float), 95 | }, 96 | 'Rails' => { 97 | 'Enabled' => true, 98 | }, 99 | }) 100 | end 101 | end 102 | 103 | context 'when not a rails project' do 104 | before do 105 | allow(Gry::RubocopAdapter).to receive(:rails?).and_return(false) 106 | end 107 | 108 | it 'returns a Hash' do 109 | base = Gry::RubocopAdapter.config_base 110 | expect(base).to match({ 111 | 'AllCops' => { 112 | 'TargetRubyVersion' => kind_of(Float), 113 | }, 114 | }) 115 | end 116 | end 117 | end 118 | 119 | describe '.find_target_files' do 120 | subject{Gry::RubocopAdapter.find_target_files} 121 | 122 | it {is_expected.to be_a Enumerable} 123 | it {is_expected.to be_all{|line| line.is_a? String}} 124 | it {is_expected.to be_none{|line| line =~ /\A\s+\z/}} 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec-power_assert' 2 | 3 | if ENV['CI'] 4 | require 'simplecov' 5 | require 'coveralls' 6 | 7 | Coveralls.wear! 8 | SimpleCov.formatter = Coveralls::SimpleCov::Formatter 9 | 10 | SimpleCov.add_filter '/spec/' 11 | 12 | SimpleCov.start 13 | end 14 | 15 | require 'gry' 16 | 17 | # This file was generated by the `rspec --init` command. Conventionally, all 18 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 19 | # The generated `.rspec` file contains `--require spec_helper` which will cause 20 | # this file to always be loaded, without a need to explicitly require it in any 21 | # files. 22 | # 23 | # Given that it is always loaded, you are encouraged to keep this file as 24 | # light-weight as possible. Requiring heavyweight dependencies from this file 25 | # will add to the boot time of your test suite on EVERY test run, even for an 26 | # individual file that may not need all of that loaded. Instead, consider making 27 | # a separate helper file that requires the additional dependencies and performs 28 | # the additional setup, and require it from the spec files that actually need 29 | # it. 30 | # 31 | # The `.rspec` file also contains a few flags that are not defaults but that 32 | # users commonly want. 33 | # 34 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 35 | RSpec.configure do |config| 36 | # rspec-expectations config goes here. You can use an alternate 37 | # assertion/expectation library such as wrong or the stdlib/minitest 38 | # assertions if you prefer. 39 | config.expect_with :rspec do |expectations| 40 | # This option will default to `true` in RSpec 4. It makes the `description` 41 | # and `failure_message` of custom matchers include text for helper methods 42 | # defined using `chain`, e.g.: 43 | # be_bigger_than(2).and_smaller_than(4).description 44 | # # => "be bigger than 2 and smaller than 4" 45 | # ...rather than: 46 | # # => "be bigger than 2" 47 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 48 | end 49 | 50 | # rspec-mocks config goes here. You can use an alternate test double 51 | # library (such as bogus or mocha) by changing the `mock_with` option here. 52 | config.mock_with :rspec do |mocks| 53 | # Prevents you from mocking or stubbing a method that does not exist on 54 | # a real object. This is generally recommended, and will default to 55 | # `true` in RSpec 4. 56 | mocks.verify_partial_doubles = true 57 | end 58 | 59 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 60 | # have no way to turn it off -- the option exists only for backwards 61 | # compatibility in RSpec 3). It causes shared context metadata to be 62 | # inherited by the metadata hash of host groups and examples, rather than 63 | # triggering implicit auto-inclusion in groups with matching metadata. 64 | config.shared_context_metadata_behavior = :apply_to_host_groups 65 | 66 | # The settings below are suggested to provide a good initial experience 67 | # with RSpec, but feel free to customize to your heart's content. 68 | =begin 69 | # This allows you to limit a spec run to individual examples or groups 70 | # you care about by tagging them with `:focus` metadata. When nothing 71 | # is tagged with `:focus`, all examples get run. RSpec also provides 72 | # aliases for `it`, `describe`, and `context` that include `:focus` 73 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 74 | config.filter_run_when_matching :focus 75 | 76 | # Allows RSpec to persist some state between runs in order to support 77 | # the `--only-failures` and `--next-failure` CLI options. We recommend 78 | # you configure your source control system to ignore this file. 79 | config.example_status_persistence_file_path = "spec/examples.txt" 80 | 81 | # Limits the available syntax to the non-monkey patched syntax that is 82 | # recommended. For more details, see: 83 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 84 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 85 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 86 | config.disable_monkey_patching! 87 | 88 | # This setting enables warnings. It's recommended, but in some cases may 89 | # be too noisy due to issues in dependencies. 90 | config.warnings = true 91 | 92 | # Many RSpec users commonly either run the entire suite or an individual 93 | # file, and it's useful to allow more verbose output when running an 94 | # individual spec file. 95 | if config.files_to_run.one? 96 | # Use the documentation formatter for detailed output, 97 | # unless a formatter has already been configured 98 | # (e.g. via a command-line flag). 99 | config.default_formatter = 'doc' 100 | end 101 | 102 | # Print the 10 slowest examples and example groups at the 103 | # end of the spec run, to help surface which specs are running 104 | # particularly slow. 105 | config.profile_examples = 10 106 | 107 | 108 | # Seed global randomization in this process using the `--seed` CLI option. 109 | # Setting this allows you to use `--seed` to deterministically reproduce 110 | # test failures related to randomization by passing the same `--seed` value 111 | # as the one that triggered the failure. 112 | Kernel.srand config.seed 113 | =end 114 | 115 | # Run specs in random order to surface order dependencies. If you find an 116 | # order dependency and want to debug it, you can fix the order by providing 117 | # the seed, which is printed after each run. 118 | # --seed 1234 119 | config.order = :random 120 | 121 | shared_context :chdir do 122 | before do 123 | Dir.chdir('lib/') 124 | end 125 | 126 | after do 127 | Dir.chdir('../') 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /spec/gry/formatter_spec.rb: -------------------------------------------------------------------------------- 1 | describe Gry::Formatter do 2 | let(:fmt){Gry::Formatter.new(display_disabled_cops: display_disabled_cops)} 3 | let(:display_disabled_cops){false} 4 | 5 | describe '.format' do 6 | def offenses(count) 7 | [{'message' => 'hoge'}] * count 8 | end 9 | 10 | context 'simple' do 11 | it 'retrns a YAML' do 12 | bill = { 13 | { 14 | 'EnforcedStyle' => 'leading', 15 | 'Enabled' => true, 16 | } => offenses(10), 17 | { 18 | 'EnforcedStyle' => 'trailing', 19 | 'Enabled' => true, 20 | } => offenses(20), 21 | } 22 | letter = { 23 | 'EnforcedStyle' => 'leading', 24 | 'Enabled' => true, 25 | } 26 | 27 | laws = [ 28 | Gry::Law.new('Style/DotPosition', bill, letter) 29 | ] 30 | 31 | expected = <<-END 32 | AllCops: 33 | TargetRubyVersion: 2.4 34 | 35 | # EnforcedStyle: leading => 10 offenses 36 | # EnforcedStyle: trailing => 20 offenses 37 | Style/DotPosition: 38 | EnforcedStyle: leading 39 | Enabled: true 40 | END 41 | 42 | expect(fmt.format(laws)).to eq expected 43 | end 44 | end 45 | 46 | context 'with a metrics cop' do 47 | it 'retrns a YAML' do 48 | bill = { 49 | { 50 | 'EnforcedStyle' => 'leading', 51 | 'Enabled' => true, 52 | } => offenses(10), 53 | } 54 | letter = { 55 | 'Max' => 40, 56 | 'Enabled' => true, 57 | } 58 | 59 | laws = [ 60 | Gry::Law.new('Metrics/LineLength', bill, letter) 61 | ] 62 | 63 | expected = <<-END 64 | AllCops: 65 | TargetRubyVersion: 2.4 66 | 67 | Metrics/LineLength: 68 | Max: 40 69 | Enabled: true 70 | END 71 | 72 | expect(fmt.format(laws)).to eq expected 73 | end 74 | end 75 | 76 | context 'has two results' do 77 | it 'returns a YAML' do 78 | laws = [ 79 | Gry::Law.new('Style/DotPosition', { 80 | { 81 | 'EnforcedStyle' => 'leading', 82 | 'Enabled' => true, 83 | } => offenses(10), 84 | { 85 | 'EnforcedStyle' => 'trailing', 86 | 'Enabled' => true, 87 | } => offenses(20), 88 | },{ 89 | 'EnforcedStyle' => 'leading', 90 | 'Enabled' => true, 91 | }), 92 | Gry::Law.new('Style/Alias', { 93 | { 94 | 'EnforcedStyle' => 'prefer_alias', 95 | 'Enabled' => true, 96 | } => offenses(42), 97 | { 98 | 'EnforcedStyle' => 'prefer_alias_method', 99 | 'Enabled' => true, 100 | } => offenses(5), 101 | },{ 102 | 'EnforcedStyle' => 'prefer_alias_method', 103 | 'Enabled' => true, 104 | }) 105 | ] 106 | 107 | expected = <<-END 108 | AllCops: 109 | TargetRubyVersion: 2.4 110 | 111 | # EnforcedStyle: leading => 10 offenses 112 | # EnforcedStyle: trailing => 20 offenses 113 | Style/DotPosition: 114 | EnforcedStyle: leading 115 | Enabled: true 116 | 117 | # EnforcedStyle: prefer_alias => 42 offenses 118 | # EnforcedStyle: prefer_alias_method => 5 offenses 119 | Style/Alias: 120 | EnforcedStyle: prefer_alias_method 121 | Enabled: true 122 | END 123 | 124 | expect(fmt.format(laws)).to eq expected 125 | end 126 | end 127 | 128 | context 'has a complex cop' do 129 | it 'returns a YAML' do 130 | bill = { 131 | { 132 | 'EnforcedStyle' => 'space', 133 | 'EnforcedStyleForEmptyBraces' => 'no_space', 134 | 'Enabled' => true, 135 | } => offenses(1), 136 | { 137 | 'EnforcedStyle' => 'space', 138 | 'EnforcedStyleForEmptyBraces' => 'space', 139 | 'Enabled' => true, 140 | } => offenses(3), 141 | { 142 | 'EnforcedStyle' => 'no_space', 143 | 'EnforcedStyleForEmptyBraces' => 'no_space', 144 | 'Enabled' => true, 145 | } => offenses(2), 146 | { 147 | 'EnforcedStyle' => 'no_space', 148 | 'EnforcedStyleForEmptyBraces' => 'space', 149 | 'Enabled' => true, 150 | } => offenses(4), 151 | } 152 | letter = { 153 | 'EnforcedStyle' => 'space', 154 | 'EnforcedStyleForEmptyBraces' => 'no_space', 155 | 'Enabled' => true, 156 | } 157 | 158 | laws = [ 159 | Gry::Law.new('Style/SpaceInsideHashLiteralBraces', bill, letter) 160 | ] 161 | 162 | expected = <<-END 163 | AllCops: 164 | TargetRubyVersion: 2.4 165 | 166 | # EnforcedStyle: space, EnforcedStyleForEmptyBraces: no_space => 1 offense 167 | # EnforcedStyle: space, EnforcedStyleForEmptyBraces: space => 3 offenses 168 | # EnforcedStyle: no_space, EnforcedStyleForEmptyBraces: no_space => 2 offenses 169 | # EnforcedStyle: no_space, EnforcedStyleForEmptyBraces: space => 4 offenses 170 | Style/SpaceInsideHashLiteralBraces: 171 | EnforcedStyle: space 172 | EnforcedStyleForEmptyBraces: no_space 173 | Enabled: true 174 | END 175 | 176 | expect(fmt.format(laws)).to eq expected 177 | end 178 | end 179 | 180 | context 'with --display-disabled-cops' do 181 | let(:display_disabled_cops){true} 182 | 183 | it 'retrns a YAML' do 184 | bill = { 185 | { 186 | 'EnforcedStyle' => 'leading', 187 | 'Enabled' => true, 188 | } => offenses(10), 189 | { 190 | 'EnforcedStyle' => 'trailing', 191 | 'Enabled' => true, 192 | } => offenses(20), 193 | } 194 | letter = { 195 | 'EnforcedStyle' => 'leading', 196 | 'Enabled' => true, 197 | } 198 | 199 | laws = [ 200 | Gry::Law.new('Style/DotPosition', bill, letter) 201 | ] 202 | 203 | expected = <<-END 204 | AllCops: 205 | TargetRubyVersion: 2.4 206 | 207 | # EnforcedStyle: leading => 10 offenses 208 | # EnforcedStyle: trailing => 20 offenses 209 | Style/DotPosition: 210 | EnforcedStyle: leading 211 | Enabled: true 212 | END 213 | 214 | expect(fmt.format(laws)).to eq expected 215 | end 216 | 217 | it 'returns a comment if disabled' do 218 | bill = { 219 | { 220 | 'EnforcedStyle' => 'leading', 221 | 'Enabled' => true, 222 | } => offenses(10), 223 | { 224 | 'EnforcedStyle' => 'trailing', 225 | 'Enabled' => true, 226 | } => offenses(20), 227 | } 228 | 229 | laws = [ 230 | Gry::Law.new('Style/DotPosition', bill, nil) 231 | ] 232 | 233 | expected = <<-END 234 | AllCops: 235 | TargetRubyVersion: 2.4 236 | 237 | # EnforcedStyle: leading => 10 offenses 238 | # EnforcedStyle: trailing => 20 offenses 239 | Style/DotPosition: 240 | Enabled: false 241 | END 242 | 243 | expect(fmt.format(laws)).to eq expected 244 | end 245 | end 246 | end 247 | end 248 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 Masataka Pocke Kuwabara 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /spec/gry/pilot_study_spec.rb: -------------------------------------------------------------------------------- 1 | describe Gry::PilotStudy do 2 | describe '#analyze' do 3 | break if ENV['DONT_RUN_SLOW_SPEC'] 4 | 5 | include_context :chdir 6 | 7 | shared_examples 'returns_a_valid_rubocop_yml' do 8 | it 'returns a valid .rubocop.yml' do 9 | result = analyze 10 | expect(result).to be_a Hash 11 | result.keys.each do |key| 12 | is_asserted_by{ key.is_a? String } 13 | end 14 | 15 | result.values.each do |value| 16 | is_asserted_by{ value.is_a? Hash } 17 | value.keys.each do |key| 18 | is_asserted_by{ key.is_a? Hash } 19 | end 20 | 21 | value.values.each do |v| 22 | is_asserted_by{ v.is_a? Array } 23 | v.each do |offence| 24 | is_asserted_by{ offence.is_a? Hash } 25 | is_asserted_by{ !offence.empty? } 26 | is_asserted_by{ offence['message'].is_a? String } 27 | end 28 | end 29 | end 30 | end 31 | end 32 | 33 | let(:analyzer){Gry::PilotStudy.new(cops, process: process)} 34 | let(:process){Parallel.processor_count} 35 | subject(:analyze){analyzer.analyze} 36 | 37 | context 'with all cops' do 38 | let(:cops){Gry::RubocopAdapter.configurable_cops} 39 | 40 | include_examples 'returns_a_valid_rubocop_yml' 41 | end 42 | 43 | context 'with fast cops only' do 44 | let(:cops) { 45 | Gry::RubocopAdapter.configurable_cops 46 | .reject{|cop| cop == 'Layout/AlignHash'} 47 | } 48 | 49 | include_examples 'returns_a_valid_rubocop_yml' 50 | end 51 | 52 | context 'non parallel mode' do 53 | let(:process){1} 54 | let(:cops){Gry::RubocopAdapter.configurable_cops} 55 | 56 | include_examples 'returns_a_valid_rubocop_yml' 57 | end 58 | end 59 | 60 | describe '#cop_configs' do 61 | shared_examples 'returns_cop_configs' do |cop_name, expected| 62 | it "returns cop configs for #{cop_name}" do 63 | analyzer = Gry::PilotStudy.new([cop_name], process: 0) 64 | config = analyzer.__send__(:cop_configs, cop_name) 65 | expect(config).to eq expected 66 | end 67 | end 68 | 69 | include_examples 'returns_cop_configs', 'Metrics/AbcSize', [ 70 | { 71 | 'Metrics/AbcSize' => { 72 | 'Enabled' => true, 73 | 'Max' => 0, 74 | }, 75 | }, 76 | ] 77 | 78 | include_examples 'returns_cop_configs', 'Style/AndOr', [ 79 | { 80 | 'Style/AndOr' => { 81 | 'EnforcedStyle' => 'always', 82 | 'Enabled' => true, 83 | }, 84 | }, 85 | { 86 | 'Style/AndOr' => { 87 | 'EnforcedStyle' => 'conditionals', 88 | 'Enabled' => true, 89 | }, 90 | } 91 | ] 92 | 93 | include_examples 'returns_cop_configs', 'Style/NumericLiteralPrefix', [ 94 | { 95 | 'Style/NumericLiteralPrefix' => { 96 | 'EnforcedOctalStyle' => 'zero_with_o', 97 | 'Enabled' => true, 98 | }, 99 | }, 100 | { 101 | 'Style/NumericLiteralPrefix' => { 102 | 'EnforcedOctalStyle' => 'zero_only', 103 | 'Enabled' => true, 104 | }, 105 | } 106 | ] 107 | 108 | include_examples 'returns_cop_configs', 'Layout/AlignHash', [ 109 | { 110 | 'Layout/AlignHash' => { 111 | 'EnforcedHashRocketStyle' => 'key', 112 | 'EnforcedColonStyle' => 'key', 113 | 'EnforcedLastArgumentHashStyle' => 'always_inspect', 114 | 'Enabled' => true, 115 | }, 116 | }, 117 | { 118 | 'Layout/AlignHash' => { 119 | 'EnforcedHashRocketStyle' => 'key', 120 | 'EnforcedColonStyle' => 'key', 121 | 'EnforcedLastArgumentHashStyle' => 'always_ignore', 122 | 'Enabled' => true, 123 | }, 124 | }, 125 | { 126 | 'Layout/AlignHash' => { 127 | 'EnforcedHashRocketStyle' => 'key', 128 | 'EnforcedColonStyle' => 'key', 129 | 'EnforcedLastArgumentHashStyle' => 'ignore_implicit', 130 | 'Enabled' => true, 131 | }, 132 | }, 133 | { 134 | 'Layout/AlignHash' => { 135 | 'EnforcedHashRocketStyle' => 'key', 136 | 'EnforcedColonStyle' => 'key', 137 | 'EnforcedLastArgumentHashStyle' => 'ignore_explicit', 138 | 'Enabled' => true, 139 | }, 140 | }, 141 | 142 | { 143 | 'Layout/AlignHash' => { 144 | 'EnforcedHashRocketStyle' => 'key', 145 | 'EnforcedColonStyle' => 'separator', 146 | 'EnforcedLastArgumentHashStyle' => 'always_inspect', 147 | 'Enabled' => true, 148 | }, 149 | }, 150 | { 151 | 'Layout/AlignHash' => { 152 | 'EnforcedHashRocketStyle' => 'key', 153 | 'EnforcedColonStyle' => 'separator', 154 | 'EnforcedLastArgumentHashStyle' => 'always_ignore', 155 | 'Enabled' => true, 156 | }, 157 | }, 158 | { 159 | 'Layout/AlignHash' => { 160 | 'EnforcedHashRocketStyle' => 'key', 161 | 'EnforcedColonStyle' => 'separator', 162 | 'EnforcedLastArgumentHashStyle' => 'ignore_implicit', 163 | 'Enabled' => true, 164 | }, 165 | }, 166 | { 167 | 'Layout/AlignHash' => { 168 | 'EnforcedHashRocketStyle' => 'key', 169 | 'EnforcedColonStyle' => 'separator', 170 | 'EnforcedLastArgumentHashStyle' => 'ignore_explicit', 171 | 'Enabled' => true, 172 | }, 173 | }, 174 | 175 | { 176 | 'Layout/AlignHash' => { 177 | 'EnforcedHashRocketStyle' => 'key', 178 | 'EnforcedColonStyle' => 'table', 179 | 'EnforcedLastArgumentHashStyle' => 'always_inspect', 180 | 'Enabled' => true, 181 | }, 182 | }, 183 | { 184 | 'Layout/AlignHash' => { 185 | 'EnforcedHashRocketStyle' => 'key', 186 | 'EnforcedColonStyle' => 'table', 187 | 'EnforcedLastArgumentHashStyle' => 'always_ignore', 188 | 'Enabled' => true, 189 | }, 190 | }, 191 | { 192 | 'Layout/AlignHash' => { 193 | 'EnforcedHashRocketStyle' => 'key', 194 | 'EnforcedColonStyle' => 'table', 195 | 'EnforcedLastArgumentHashStyle' => 'ignore_implicit', 196 | 'Enabled' => true, 197 | }, 198 | }, 199 | { 200 | 'Layout/AlignHash' => { 201 | 'EnforcedHashRocketStyle' => 'key', 202 | 'EnforcedColonStyle' => 'table', 203 | 'EnforcedLastArgumentHashStyle' => 'ignore_explicit', 204 | 'Enabled' => true, 205 | }, 206 | }, 207 | 208 | 209 | { 210 | 'Layout/AlignHash' => { 211 | 'EnforcedHashRocketStyle' => 'separator', 212 | 'EnforcedColonStyle' => 'key', 213 | 'EnforcedLastArgumentHashStyle' => 'always_inspect', 214 | 'Enabled' => true, 215 | }, 216 | }, 217 | { 218 | 'Layout/AlignHash' => { 219 | 'EnforcedHashRocketStyle' => 'separator', 220 | 'EnforcedColonStyle' => 'key', 221 | 'EnforcedLastArgumentHashStyle' => 'always_ignore', 222 | 'Enabled' => true, 223 | }, 224 | }, 225 | { 226 | 'Layout/AlignHash' => { 227 | 'EnforcedHashRocketStyle' => 'separator', 228 | 'EnforcedColonStyle' => 'key', 229 | 'EnforcedLastArgumentHashStyle' => 'ignore_implicit', 230 | 'Enabled' => true, 231 | }, 232 | }, 233 | { 234 | 'Layout/AlignHash' => { 235 | 'EnforcedHashRocketStyle' => 'separator', 236 | 'EnforcedColonStyle' => 'key', 237 | 'EnforcedLastArgumentHashStyle' => 'ignore_explicit', 238 | 'Enabled' => true, 239 | }, 240 | }, 241 | 242 | { 243 | 'Layout/AlignHash' => { 244 | 'EnforcedHashRocketStyle' => 'separator', 245 | 'EnforcedColonStyle' => 'separator', 246 | 'EnforcedLastArgumentHashStyle' => 'always_inspect', 247 | 'Enabled' => true, 248 | }, 249 | }, 250 | { 251 | 'Layout/AlignHash' => { 252 | 'EnforcedHashRocketStyle' => 'separator', 253 | 'EnforcedColonStyle' => 'separator', 254 | 'EnforcedLastArgumentHashStyle' => 'always_ignore', 255 | 'Enabled' => true, 256 | }, 257 | }, 258 | { 259 | 'Layout/AlignHash' => { 260 | 'EnforcedHashRocketStyle' => 'separator', 261 | 'EnforcedColonStyle' => 'separator', 262 | 'EnforcedLastArgumentHashStyle' => 'ignore_implicit', 263 | 'Enabled' => true, 264 | }, 265 | }, 266 | { 267 | 'Layout/AlignHash' => { 268 | 'EnforcedHashRocketStyle' => 'separator', 269 | 'EnforcedColonStyle' => 'separator', 270 | 'EnforcedLastArgumentHashStyle' => 'ignore_explicit', 271 | 'Enabled' => true, 272 | }, 273 | }, 274 | 275 | { 276 | 'Layout/AlignHash' => { 277 | 'EnforcedHashRocketStyle' => 'separator', 278 | 'EnforcedColonStyle' => 'table', 279 | 'EnforcedLastArgumentHashStyle' => 'always_inspect', 280 | 'Enabled' => true, 281 | }, 282 | }, 283 | { 284 | 'Layout/AlignHash' => { 285 | 'EnforcedHashRocketStyle' => 'separator', 286 | 'EnforcedColonStyle' => 'table', 287 | 'EnforcedLastArgumentHashStyle' => 'always_ignore', 288 | 'Enabled' => true, 289 | }, 290 | }, 291 | { 292 | 'Layout/AlignHash' => { 293 | 'EnforcedHashRocketStyle' => 'separator', 294 | 'EnforcedColonStyle' => 'table', 295 | 'EnforcedLastArgumentHashStyle' => 'ignore_implicit', 296 | 'Enabled' => true, 297 | }, 298 | }, 299 | { 300 | 'Layout/AlignHash' => { 301 | 'EnforcedHashRocketStyle' => 'separator', 302 | 'EnforcedColonStyle' => 'table', 303 | 'EnforcedLastArgumentHashStyle' => 'ignore_explicit', 304 | 'Enabled' => true, 305 | }, 306 | }, 307 | 308 | 309 | { 310 | 'Layout/AlignHash' => { 311 | 'EnforcedHashRocketStyle' => 'table', 312 | 'EnforcedColonStyle' => 'key', 313 | 'EnforcedLastArgumentHashStyle' => 'always_inspect', 314 | 'Enabled' => true, 315 | }, 316 | }, 317 | { 318 | 'Layout/AlignHash' => { 319 | 'EnforcedHashRocketStyle' => 'table', 320 | 'EnforcedColonStyle' => 'key', 321 | 'EnforcedLastArgumentHashStyle' => 'always_ignore', 322 | 'Enabled' => true, 323 | }, 324 | }, 325 | { 326 | 'Layout/AlignHash' => { 327 | 'EnforcedHashRocketStyle' => 'table', 328 | 'EnforcedColonStyle' => 'key', 329 | 'EnforcedLastArgumentHashStyle' => 'ignore_implicit', 330 | 'Enabled' => true, 331 | }, 332 | }, 333 | { 334 | 'Layout/AlignHash' => { 335 | 'EnforcedHashRocketStyle' => 'table', 336 | 'EnforcedColonStyle' => 'key', 337 | 'EnforcedLastArgumentHashStyle' => 'ignore_explicit', 338 | 'Enabled' => true, 339 | }, 340 | }, 341 | 342 | { 343 | 'Layout/AlignHash' => { 344 | 'EnforcedHashRocketStyle' => 'table', 345 | 'EnforcedColonStyle' => 'separator', 346 | 'EnforcedLastArgumentHashStyle' => 'always_inspect', 347 | 'Enabled' => true, 348 | }, 349 | }, 350 | { 351 | 'Layout/AlignHash' => { 352 | 'EnforcedHashRocketStyle' => 'table', 353 | 'EnforcedColonStyle' => 'separator', 354 | 'EnforcedLastArgumentHashStyle' => 'always_ignore', 355 | 'Enabled' => true, 356 | }, 357 | }, 358 | { 359 | 'Layout/AlignHash' => { 360 | 'EnforcedHashRocketStyle' => 'table', 361 | 'EnforcedColonStyle' => 'separator', 362 | 'EnforcedLastArgumentHashStyle' => 'ignore_implicit', 363 | 'Enabled' => true, 364 | }, 365 | }, 366 | { 367 | 'Layout/AlignHash' => { 368 | 'EnforcedHashRocketStyle' => 'table', 369 | 'EnforcedColonStyle' => 'separator', 370 | 'EnforcedLastArgumentHashStyle' => 'ignore_explicit', 371 | 'Enabled' => true, 372 | }, 373 | }, 374 | 375 | { 376 | 'Layout/AlignHash' => { 377 | 'EnforcedHashRocketStyle' => 'table', 378 | 'EnforcedColonStyle' => 'table', 379 | 'EnforcedLastArgumentHashStyle' => 'always_inspect', 380 | 'Enabled' => true, 381 | }, 382 | }, 383 | { 384 | 'Layout/AlignHash' => { 385 | 'EnforcedHashRocketStyle' => 'table', 386 | 'EnforcedColonStyle' => 'table', 387 | 'EnforcedLastArgumentHashStyle' => 'always_ignore', 388 | 'Enabled' => true, 389 | }, 390 | }, 391 | { 392 | 'Layout/AlignHash' => { 393 | 'EnforcedHashRocketStyle' => 'table', 394 | 'EnforcedColonStyle' => 'table', 395 | 'EnforcedLastArgumentHashStyle' => 'ignore_implicit', 396 | 'Enabled' => true, 397 | }, 398 | }, 399 | { 400 | 'Layout/AlignHash' => { 401 | 'EnforcedHashRocketStyle' => 'table', 402 | 'EnforcedColonStyle' => 'table', 403 | 'EnforcedLastArgumentHashStyle' => 'ignore_explicit', 404 | 'Enabled' => true, 405 | }, 406 | }, 407 | 408 | ] 409 | 410 | end 411 | end 412 | --------------------------------------------------------------------------------