standard 0.0.34

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of standard might be problematic. Click here for more details.

@@ -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