standard 0.0.37

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ inherit_from: ./ruby-1.9.yml
2
+
3
+ Style/Lambda:
4
+ Enabled: false
5
+
6
+ Style/HashSyntax:
7
+ EnforcedStyle: hash_rockets
8
+
@@ -0,0 +1,4 @@
1
+ inherit_from: ./ruby-2.2.yml
2
+
3
+ Style/Encoding:
4
+ Enabled: false
@@ -0,0 +1,8 @@
1
+ inherit_from: ./base.yml
2
+
3
+ AllCops:
4
+ TargetRubyVersion: 2.2
5
+
6
+ Layout:
7
+ IndentHeredoc:
8
+ Enabled: false
data/exe/standardrb ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift("#{__dir__}/../lib")
4
+
5
+ require "standard"
6
+
7
+ exit Standard::Cli.new(ARGV).run
@@ -0,0 +1,26 @@
1
+ require_relative "loads_yaml_config"
2
+ require_relative "merges_settings"
3
+ require_relative "creates_config_store"
4
+
5
+ module Standard
6
+ Config = Struct.new(:runner, :paths, :rubocop_options, :rubocop_config_store)
7
+
8
+ class BuildsConfig
9
+ def initialize
10
+ @loads_yaml_config = LoadsYamlConfig.new
11
+ @merges_settings = MergesSettings.new
12
+ @creates_config_store = CreatesConfigStore.new
13
+ end
14
+
15
+ def call(argv, search_path = Dir.pwd)
16
+ standard_config = @loads_yaml_config.call(argv, search_path)
17
+ settings = @merges_settings.call(argv, standard_config)
18
+ Config.new(
19
+ settings.runner,
20
+ settings.paths,
21
+ settings.options,
22
+ @creates_config_store.call(standard_config)
23
+ )
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ require_relative "builds_config"
2
+ require_relative "loads_runner"
3
+
4
+ module Standard
5
+ class Cli
6
+ SUCCESS_STATUS_CODE = 0
7
+ FAILURE_STATUS_CODE = 1
8
+
9
+ def initialize(argv)
10
+ @argv = argv
11
+ @builds_config = BuildsConfig.new
12
+ @loads_runner = LoadsRunner.new
13
+ end
14
+
15
+ def run
16
+ config = @builds_config.call(@argv)
17
+
18
+ success = @loads_runner.call(config.runner).call(config)
19
+
20
+ success ? SUCCESS_STATUS_CODE : FAILURE_STATUS_CODE
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,162 @@
1
+ module RuboCop::Cop
2
+ module Standard
3
+ class SemanticBlocks < RuboCop::Cop::Cop
4
+ include RuboCop::Cop::IgnoredMethods
5
+
6
+ def on_send(node)
7
+ return unless node.arguments?
8
+ return if node.parenthesized? || node.operator_method?
9
+
10
+ node.arguments.each do |arg|
11
+ get_blocks(arg) do |block|
12
+ # If there are no parentheses around the arguments, then braces
13
+ # and do-end have different meaning due to how they bind, so we
14
+ # allow either.
15
+ ignore_node(block)
16
+ end
17
+ end
18
+ end
19
+
20
+ def on_block(node)
21
+ return if ignored_node?(node) || proper_block_style?(node)
22
+
23
+ add_offense(node, location: :begin)
24
+ end
25
+
26
+ def autocorrect(node)
27
+ return if correction_would_break_code?(node)
28
+
29
+ if node.single_line?
30
+ replace_do_end_with_braces(node.loc)
31
+ elsif node.braces?
32
+ replace_braces_with_do_end(node.loc)
33
+ else
34
+ replace_do_end_with_braces(node.loc)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def message(node)
41
+ if node.single_line?
42
+ "Prefer `{...}` over `do...end` for single-line blocks."
43
+ elsif node.loc.begin.source == "{"
44
+ "Prefer `do...end` over `{...}` for procedural blocks."
45
+ else
46
+ "Prefer `{...}` over `do...end` for functional blocks."
47
+ end
48
+ end
49
+
50
+ def replace_braces_with_do_end(loc)
51
+ b = loc.begin
52
+ e = loc.end
53
+
54
+ lambda do |corrector|
55
+ corrector.insert_before(b, " ") unless whitespace_before?(b)
56
+ corrector.insert_before(e, " ") unless whitespace_before?(e)
57
+ corrector.insert_after(b, " ") unless whitespace_after?(b)
58
+ corrector.replace(b, "do")
59
+ corrector.replace(e, "end")
60
+ end
61
+ end
62
+
63
+ def replace_do_end_with_braces(loc)
64
+ b = loc.begin
65
+ e = loc.end
66
+
67
+ lambda do |corrector|
68
+ corrector.insert_after(b, " ") unless whitespace_after?(b, 2)
69
+
70
+ corrector.replace(b, "{")
71
+ corrector.replace(e, "}")
72
+ end
73
+ end
74
+
75
+ def whitespace_before?(range)
76
+ range.source_buffer.source[range.begin_pos - 1, 1] =~ /\s/
77
+ end
78
+
79
+ def whitespace_after?(range, length = 1)
80
+ range.source_buffer.source[range.begin_pos + length, 1] =~ /\s/
81
+ end
82
+
83
+ def get_blocks(node, &block)
84
+ case node.type
85
+ when :block
86
+ yield node
87
+ when :send
88
+ get_blocks(node.receiver, &block) if node.receiver
89
+ when :hash
90
+ # A hash which is passed as method argument may have no braces
91
+ # In that case, one of the K/V pairs could contain a block node
92
+ # which could change in meaning if do...end replaced {...}
93
+ return if node.braces?
94
+
95
+ node.each_child_node { |child| get_blocks(child, &block) }
96
+ when :pair
97
+ node.each_child_node { |child| get_blocks(child, &block) }
98
+ end
99
+ end
100
+
101
+ def proper_block_style?(node)
102
+ method_name = node.method_name
103
+
104
+ if ignored_method?(method_name)
105
+ true
106
+ elsif node.single_line?
107
+ node.braces?
108
+ elsif node.braces?
109
+ !procedural_method?(method_name) &&
110
+ (functional_method?(method_name) || functional_block?(node))
111
+ else
112
+ procedural_method?(method_name) || !return_value_used?(node)
113
+ end
114
+ end
115
+
116
+ def correction_would_break_code?(node)
117
+ return unless node.keywords?
118
+
119
+ node.send_node.arguments? && !node.send_node.parenthesized?
120
+ end
121
+
122
+ def functional_method?(method_name)
123
+ cop_config["FunctionalMethods"].map(&:to_sym).include?(method_name)
124
+ end
125
+
126
+ def functional_block?(node)
127
+ return_value_used?(node) || return_value_of_scope?(node)
128
+ end
129
+
130
+ def procedural_method?(method_name)
131
+ cop_config["ProceduralMethods"].map(&:to_sym).include?(method_name)
132
+ end
133
+
134
+ def return_value_used?(node)
135
+ return unless node.parent
136
+
137
+ # If there are parentheses around the block, check if that
138
+ # is being used.
139
+ if node.parent.begin_type?
140
+ return_value_used?(node.parent)
141
+ else
142
+ node.parent.assignment? || node.parent.send_type?
143
+ end
144
+ end
145
+
146
+ def return_value_of_scope?(node)
147
+ return unless node.parent
148
+
149
+ conditional?(node.parent) || array_or_range?(node.parent) ||
150
+ node.parent.children.last == node
151
+ end
152
+
153
+ def conditional?(node)
154
+ node.if_type? || node.or_type? || node.and_type?
155
+ end
156
+
157
+ def array_or_range?(node)
158
+ node.array_type? || node.irange_type? || node.erange_type?
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,26 @@
1
+ require "pathname"
2
+
3
+ class Standard::CreatesConfigStore
4
+ class AssignsRubocopYaml
5
+ def call(config_store, standard_config)
6
+ config_store.options_config = rubocop_yaml_path(standard_config[:ruby_version])
7
+ config_store.instance_variable_get("@options_config")
8
+ end
9
+
10
+ private
11
+
12
+ def rubocop_yaml_path(desired_version)
13
+ file_name = if desired_version < Gem::Version.new("1.9")
14
+ "ruby-1.8.yml"
15
+ elsif desired_version < Gem::Version.new("2.0")
16
+ "ruby-1.9.yml"
17
+ elsif desired_version < Gem::Version.new("2.3")
18
+ "ruby-2.2.yml"
19
+ else
20
+ "base.yml"
21
+ end
22
+
23
+ Pathname.new(__dir__).join("../../../config/#{file_name}")
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,45 @@
1
+ class Standard::CreatesConfigStore
2
+ class ConfiguresIgnoredPaths
3
+ DEFAULT_IGNORES = [
4
+ # Match RuboCop's defaults: https://github.com/rubocop-hq/rubocop/blob/v0.61.1/config/default.yml#L60-L63
5
+ ".git/**/*",
6
+ "node_modules/**/*",
7
+ "vendor/**/*",
8
+ # Standard's own default ignores:
9
+ "bin/*",
10
+ "db/schema.rb",
11
+ "tmp/**/*",
12
+ ].map { |path| [path, ["AllCops"]] }.freeze
13
+
14
+ def call(options_config, standard_config)
15
+ ignored_patterns(standard_config).each do |(path, cops)|
16
+ cops.each do |cop|
17
+ options_config[cop] ||= {}
18
+ options_config[cop]["Exclude"] ||= []
19
+ options_config[cop]["Exclude"] |= [
20
+ absolutify(standard_config[:config_root], path),
21
+ ]
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def ignored_patterns(standard_config)
29
+ (standard_config[:default_ignores] ? DEFAULT_IGNORES : []) +
30
+ standard_config[:ignore]
31
+ end
32
+
33
+ def absolutify(config_root, path)
34
+ if !absolute?(path)
35
+ File.expand_path(File.join(config_root || Dir.pwd, path))
36
+ else
37
+ path
38
+ end
39
+ end
40
+
41
+ def absolute?(path)
42
+ path =~ %r{\A([A-Z]:)?/}
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ class Standard::CreatesConfigStore
2
+ class SetsTargetRubyVersion
3
+ def call(options_config, standard_config)
4
+ options_config["AllCops"]["TargetRubyVersion"] = floatify_version(
5
+ max_rubocop_supported_version(standard_config[:ruby_version])
6
+ )
7
+ end
8
+
9
+ private
10
+
11
+ def max_rubocop_supported_version(desired_version)
12
+ rubocop_supported_version = Gem::Version.new("2.2")
13
+ if desired_version < rubocop_supported_version
14
+ rubocop_supported_version
15
+ else
16
+ desired_version
17
+ end
18
+ end
19
+
20
+ def floatify_version(version)
21
+ major, minor = version.segments
22
+ "#{major}.#{minor}".to_f # lol
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ require "rubocop"
2
+
3
+ require_relative "creates_config_store/assigns_rubocop_yaml"
4
+ require_relative "creates_config_store/sets_target_ruby_version"
5
+ require_relative "creates_config_store/configures_ignored_paths"
6
+
7
+ module Standard
8
+ class CreatesConfigStore
9
+ def initialize
10
+ @assigns_rubocop_yaml = AssignsRubocopYaml.new
11
+ @sets_target_ruby_version = SetsTargetRubyVersion.new
12
+ @configures_ignored_paths = ConfiguresIgnoredPaths.new
13
+ end
14
+
15
+ def call(standard_config)
16
+ RuboCop::ConfigStore.new.tap do |config_store|
17
+ options_config = @assigns_rubocop_yaml.call(config_store, standard_config)
18
+ @sets_target_ruby_version.call(options_config, standard_config)
19
+ @configures_ignored_paths.call(options_config, standard_config)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ module Standard
2
+ class DetectsFixability
3
+ def call(offenses)
4
+ offenses.any? { |offense|
5
+ cop = cop_instance(offense.cop_name)
6
+ cop.support_autocorrect? && safe?(cop)
7
+ }
8
+ end
9
+
10
+ private
11
+
12
+ def cop_instance(cop_name)
13
+ RuboCop::Cop.const_get(cop_name.gsub("/", "::")).new
14
+ end
15
+
16
+ def safe?(cop)
17
+ cop.cop_config.fetch("SafeAutoCorrect", true)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ require "pathname"
2
+
3
+ module Standard
4
+ class FileFinder
5
+ def call(name, search_path)
6
+ Pathname.new(search_path).expand_path.ascend do |path|
7
+ if (file = path + name).exist?
8
+ return file.to_s
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,79 @@
1
+ require "rubocop"
2
+ require_relative "detects_fixability"
3
+
4
+ module Standard
5
+ class Formatter < RuboCop::Formatter::BaseFormatter
6
+ CALL_TO_ACTION_MESSAGE = <<-CALL_TO_ACTION.gsub(/^ {6}/, "")
7
+ Notice: Disagree with these rules? While StandardRB is pre-1.0.0, feel free to submit suggestions to:
8
+ https://github.com/testdouble/standard/issues/new
9
+ CALL_TO_ACTION
10
+
11
+ def initialize(*args)
12
+ super
13
+ @detects_fixability = DetectsFixability.new
14
+ @header_printed_already = false
15
+ @fix_suggestion_printed_already = false
16
+ @any_uncorrected_offenses = false
17
+ end
18
+
19
+ def file_finished(file, offenses)
20
+ return unless (uncorrected_offenses = offenses.reject(&:corrected?)).any?
21
+ @any_uncorrected_offenses = true
22
+
23
+ print_header_once
24
+ print_fix_suggestion_once(uncorrected_offenses)
25
+
26
+ uncorrected_offenses.each do |o|
27
+ output.printf(" %s:%d:%d: %s\n", path_to(file), o.line, o.real_column, o.message.tr("\n", " "))
28
+ end
29
+ end
30
+
31
+ def finished(_)
32
+ print_call_for_feedback if @any_uncorrected_offenses
33
+ end
34
+
35
+ private
36
+
37
+ def print_header_once
38
+ return if @header_printed_already
39
+
40
+ output.print <<-HEADER.gsub(/^ {8}/, "")
41
+ standard: Use Ruby Standard Style (https://github.com/testdouble/standard)
42
+ HEADER
43
+
44
+ @header_printed_already = true
45
+ end
46
+
47
+ def print_fix_suggestion_once(offenses)
48
+ if !@fix_suggestion_printed_already && should_suggest_fix?(offenses)
49
+ command = if File.split($PROGRAM_NAME).last == "rake"
50
+ "rake standard:fix"
51
+ else
52
+ "standardrb --fix"
53
+ end
54
+
55
+ output.print <<-HEADER.gsub(/^ {10}/, "")
56
+ standard: Run `#{command}` to automatically fix some problems.
57
+ HEADER
58
+ @fix_suggestion_printed_already = true
59
+ end
60
+ end
61
+
62
+ def path_to(file)
63
+ Pathname.new(file).relative_path_from(Pathname.new(Dir.pwd))
64
+ end
65
+
66
+ def print_call_for_feedback
67
+ output.print "\n"
68
+ output.print CALL_TO_ACTION_MESSAGE
69
+ end
70
+
71
+ def auto_correct_option_provided?
72
+ options[:auto_correct] || options[:safe_auto_correct]
73
+ end
74
+
75
+ def should_suggest_fix?(offenses)
76
+ !auto_correct_option_provided? && @detects_fixability.call(offenses)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,9 @@
1
+ module Standard
2
+ class LoadsRunner
3
+ # Warning: clever metaprogramming. 99% of the time this is Runners::Rubocop
4
+ def call(command)
5
+ require_relative "runners/#{command}"
6
+ ::Standard::Runners.const_get(command.to_s.capitalize).new
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,60 @@
1
+ require "yaml"
2
+ require "pathname"
3
+ require_relative "file_finder"
4
+ require_relative "parses_cli_option"
5
+
6
+ module Standard
7
+ class LoadsYamlConfig
8
+ def initialize
9
+ @parses_cli_option = ParsesCliOption.new
10
+ end
11
+
12
+ def call(argv, search_path)
13
+ yaml_path = @parses_cli_option.call(argv, "--config") ||
14
+ FileFinder.new.call(".standard.yml", search_path)
15
+ construct_config(yaml_path, load_standard_yaml(yaml_path))
16
+ end
17
+
18
+ private
19
+
20
+ def load_standard_yaml(yaml_path)
21
+ if yaml_path
22
+ YAML.load_file(yaml_path) || {}
23
+ else
24
+ {}
25
+ end
26
+ end
27
+
28
+ def construct_config(yaml_path, standard_yaml)
29
+ {
30
+ ruby_version: Gem::Version.new((standard_yaml["ruby_version"] || RUBY_VERSION)),
31
+ fix: !!standard_yaml["fix"],
32
+ format: standard_yaml["format"],
33
+ parallel: !!standard_yaml["parallel"],
34
+ ignore: expand_ignore_config(standard_yaml["ignore"]),
35
+ default_ignores: standard_yaml.key?("default_ignores") ? !!standard_yaml["default_ignores"] : true,
36
+ config_root: yaml_path ? Pathname.new(yaml_path).dirname.to_s : nil,
37
+ }
38
+ end
39
+
40
+ def expand_ignore_config(ignore_config)
41
+ arrayify(ignore_config).map { |rule|
42
+ if rule.is_a?(String)
43
+ [rule, ["AllCops"]]
44
+ elsif rule.is_a?(Hash)
45
+ rule.entries.first
46
+ end
47
+ }
48
+ end
49
+
50
+ def arrayify(object)
51
+ if object.nil?
52
+ []
53
+ elsif object.respond_to?(:to_ary)
54
+ object.to_ary || [object]
55
+ else
56
+ [object]
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,64 @@
1
+ require "rubocop"
2
+
3
+ module Standard
4
+ class MergesSettings
5
+ Settings = Struct.new(:runner, :options, :paths)
6
+
7
+ def call(argv, standard_yaml)
8
+ standard_argv, rubocop_argv = separate_argv(argv)
9
+ standard_cli_flags = parse_standard_argv(standard_argv)
10
+ rubocop_cli_flags, lint_paths = RuboCop::Options.new.parse(rubocop_argv)
11
+
12
+ Settings.new(
13
+ determine_command(standard_argv),
14
+ merge(standard_yaml, standard_cli_flags, without_banned(rubocop_cli_flags)),
15
+ lint_paths
16
+ )
17
+ end
18
+
19
+ private
20
+
21
+ def separate_argv(argv)
22
+ argv.partition { |flag|
23
+ ["--fix", "--no-fix", "--version", "-v", "--help", "-h"].include?(flag)
24
+ }
25
+ end
26
+
27
+ def parse_standard_argv(argv)
28
+ argv.each_with_object({}) { |arg, cli_flags|
29
+ if arg == "--fix"
30
+ cli_flags[:auto_correct] = true
31
+ cli_flags[:safe_auto_correct] = true
32
+ elsif arg == "--no-fix"
33
+ cli_flags[:auto_correct] = false
34
+ cli_flags[:safe_auto_correct] = false
35
+ end
36
+ }
37
+ end
38
+
39
+ def determine_command(argv)
40
+ if (argv & ["--help", "-h"]).any?
41
+ :help
42
+ elsif (argv & ["--version", "-v"]).any?
43
+ :version
44
+ else
45
+ :rubocop
46
+ end
47
+ end
48
+
49
+ def merge(standard_yaml, standard_cli_flags, rubocop_cli_flags)
50
+ {
51
+ auto_correct: standard_yaml[:fix],
52
+ safe_auto_correct: standard_yaml[:fix],
53
+ formatters: [[standard_yaml[:format] || "Standard::Formatter", nil]],
54
+ parallel: standard_yaml[:parallel],
55
+ }.merge(standard_cli_flags).merge(rubocop_cli_flags)
56
+ end
57
+
58
+ def without_banned(rubocop_cli_flags)
59
+ rubocop_cli_flags.tap do |flags|
60
+ flags.delete(:config)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,21 @@
1
+ require "pathname"
2
+
3
+ module Standard
4
+ class ParsesCliOption
5
+ def call(argv, option_name)
6
+ return unless (config_file = argv_value_for(argv, option_name))
7
+
8
+ resolved_config = Pathname.new(config_file)
9
+ if resolved_config.exist?
10
+ resolved_config.expand_path
11
+ else
12
+ raise "Configuration file \"#{resolved_config.expand_path}\" not found."
13
+ end
14
+ end
15
+
16
+ def argv_value_for(argv, option_name)
17
+ return unless (index = argv.index(option_name))
18
+ argv[index + 1]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ require "pathname"
2
+
3
+ module Standard
4
+ class Railtie < Rails::Railtie
5
+ railtie_name :standard
6
+
7
+ rake_tasks do
8
+ load Pathname.new(__dir__).join("rake.rb")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ module Standard
2
+ module RakeSupport
3
+ # Allow command line flags set in STANDARDOPTS (like MiniTest's TESTOPTS)
4
+ def self.argvify
5
+ if ENV["STANDARDOPTS"]
6
+ ENV["STANDARDOPTS"].split(/\s+/)
7
+ else
8
+ []
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ desc "Lint with the Standard Ruby style guide"
15
+ task :standard do
16
+ require "standard"
17
+ exit_code = Standard::Cli.new(Standard::RakeSupport.argvify).run
18
+ fail unless exit_code == 0
19
+ end
20
+
21
+ desc "Lint and automatically fix with the Standard Ruby style guide"
22
+ task :"standard:fix" do
23
+ require "standard"
24
+ exit_code = Standard::Cli.new(Standard::RakeSupport.argvify + ["--fix"]).run
25
+ fail unless exit_code == 0
26
+ end
@@ -0,0 +1,7 @@
1
+ module RuboCop
2
+ class Cop::Lint::AssignmentInCondition
3
+ def message(_)
4
+ "Wrap assignment in parentheses if intentional"
5
+ end
6
+ end
7
+ end