nucop 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f9fc165f5dbcc9624bfd5ed3c1c372f3f81ff325ca44d59990c4db36a2f0da39
4
+ data.tar.gz: a10769b0dc3d5de55c23442b4d27ea6bedecde3db859b3e679b5583c2b3e9846
5
+ SHA512:
6
+ metadata.gz: 43218b9441df727be126479a83813cbfc048af1a472f1fc1a5dc365871eaad0cf95bab8b091abf34238243dd3f3843fe0d0408a57e59a17193de288f0e2ed697
7
+ data.tar.gz: 986be2c74320253d2746d25229fd9ad724f61029e1200d7b447b7373aa83ec813921af6bd3745eff5d0cd92e5f51c69a7176881bde42a2f95526cfc1311bd6ab
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift("#{__dir__}/../lib")
4
+
5
+ require "nucop"
6
+ require "nucop/cli"
7
+
8
+ Nucop::CLI.start(ARGV)
@@ -0,0 +1,11 @@
1
+ require "nucop/version"
2
+
3
+ require "rubocop"
4
+ require "git_diff_parser"
5
+
6
+ Dir[File.join(__dir__, "nucop/helpers/**/*.rb")].each { |f| require f }
7
+ Dir[File.join(__dir__, "nucop/formatters/**/*.rb")].each { |f| require f }
8
+ Dir[File.join(__dir__, "nucop/cops/**/*.rb")].each { |f| require f }
9
+
10
+ module Nucop
11
+ end
@@ -0,0 +1,268 @@
1
+ require "thor"
2
+ require "open3"
3
+
4
+ RUBOCOP_DEFAULT_CONFIG_FILE = ".rubocop.yml"
5
+ CONFIGURATION_FILEPATH = ".nucop.yml"
6
+
7
+ module Nucop
8
+ class CLI < Thor
9
+ desc "diff_enforced", "run RuboCop on the current diff using only the enforced cops"
10
+ method_option "commit-spec", default: "origin/master", desc: "the commit used to determine the diff."
11
+ method_option "auto-correct", type: :boolean, default: false, desc: "runs RuboCop with auto-correct option"
12
+ method_option "junit_report", type: :string, default: "", desc: "runs RuboCop with junit formatter option"
13
+ def diff_enforced
14
+ invoke :diff, nil, options.merge(only: cops_to_enforce.join(","))
15
+ end
16
+
17
+ desc "diff", "run RuboCop on the current diff"
18
+ method_option "commit-spec", default: "origin/master", desc: "the commit used to determine the diff."
19
+ method_option "only", desc: "run only specified cop(s) and/or cops in the specified departments"
20
+ method_option "auto-correct", type: :boolean, default: false, desc: "runs RuboCop with auto-correct option"
21
+ method_option "ignore", type: :boolean, default: true, desc: "ignores files specified in #{options[:diffignore_file]}"
22
+ method_option "added-only", type: :boolean, default: false, desc: "runs RuboCop only on files that have been added (not on files that have been modified)"
23
+ method_option "exit", type: :boolean, default: true, desc: "disable to prevent task from exiting. Used by other Thor tasks when invoking this task, to prevent parent task from exiting"
24
+ def diff
25
+ puts "Running on files changed relative to '#{options[:"commit-spec"]}' (specify using the 'commit-spec' option)"
26
+ diff_filter = options[:"added-only"] ? "A" : "d"
27
+ diff_base = capture_std_out("git merge-base HEAD #{options[:"commit-spec"]}").chomp
28
+
29
+ files, diff_status = Open3.capture2("git diff #{diff_base} --diff-filter=#{diff_filter} --name-only | grep \"\\.rb$\"")
30
+
31
+ if diff_status != 0
32
+ if options[:exit]
33
+ puts "There are no rb files present in diff. Exiting."
34
+ exit 0
35
+ else
36
+ puts "There are no rb files present in diff."
37
+ return true
38
+ end
39
+ end
40
+
41
+ if options[:ignore] && File.exist?(options[:diffignore_file]) && !File.zero?(options[:diffignore_file])
42
+ files, non_ignored_diff_status = Open3.capture2("grep -v -f #{options[:diffignore_file]}", stdin_data: files)
43
+
44
+ if non_ignored_diff_status != 0
45
+ if options[:exit]
46
+ puts "There are no non-ignored rb files present in diff. Exiting."
47
+ exit 0
48
+ else
49
+ puts "There are no non-ignored rb files present in diff."
50
+ return true
51
+ end
52
+ end
53
+ end
54
+
55
+ no_violations_detected = invoke :rubocop, [multi_line_to_single_line(files)], options
56
+
57
+ exit 1 unless no_violations_detected
58
+ return true unless options[:exit]
59
+ exit 0
60
+ end
61
+
62
+ desc "rubocop", "run RuboCop on files provided"
63
+ method_option "only", desc: "run only specified cop(s) and/or cops in the specified departments"
64
+ method_option "auto-correct", type: :boolean, default: false, desc: "runs RuboCop with auto-correct option"
65
+ method_option "exclude-backlog", type: :boolean, default: false, desc: "when true, uses config which excludes violations in the RuboCop backlog"
66
+ def rubocop(files = nil)
67
+ print_cops_being_run(options[:only])
68
+ config_file = options[:"exclude-backlog"] ? RUBOCOP_DEFAULT_CONFIG_FILE : options[:rubocop_todo_config_file]
69
+ junit_report_path = options[:"junit_report"]
70
+ junit_report_options = junit_report_path.to_s.empty? ? "" : "--format Nucop::Formatters::JUnitFormatter --out #{junit_report_path} --format progress"
71
+
72
+ rubocop_requires = [
73
+ "--require rubocop-rspec",
74
+ "--require rubocop-performance",
75
+ "--require rubocop-rails"
76
+ ]
77
+
78
+ system("bundle exec rubocop --parallel #{rubocop_requires.join(' ')} #{junit_report_options} --force-exclusion --config #{config_file} #{pass_through_option(options, 'auto-correct')} #{pass_through_flag(options, 'only')} #{files}")
79
+ end
80
+
81
+ desc "regen_backlog", "update the RuboCop backlog, disabling offending files and excluding all cops with over 500 violating files."
82
+ method_option "exclude-limit", type: :numeric, default: 500, desc: "Limit files listed to this limit. Passed to RuboCop"
83
+ def regen_backlog
84
+ regenerate_rubocop_todos
85
+ update_enforced_cops
86
+ end
87
+
88
+ desc "update_enforced", "update the enforced cops list with file with cops that no longer have violations"
89
+ def update_enforced
90
+ update_enforced_cops
91
+ end
92
+
93
+ desc "modified_lines", "display RuboCop violations for ONLY modified lines"
94
+ method_option "commit-spec", default: "master", desc: "the commit used to determine the diff."
95
+ def modified_lines
96
+ diff_files, diff_status = Open3.capture2("git diff #{options[:'commit-spec']} --diff-filter=d --name-only | grep \"\\.rb$\"")
97
+
98
+ exit 1 unless diff_status.exitstatus.zero?
99
+
100
+ command = [
101
+ "bundle exec rubocop",
102
+ "--parallel",
103
+ "--format Nucop::Formatters::GitDiffFormatter",
104
+ "--config #{options[:rubocop_todo_config_file]}",
105
+ multi_line_to_single_line(diff_files).to_s
106
+ ].join(" ")
107
+
108
+ # HACK: use ENVVAR to parameterize GitDiffFormatter
109
+ system({ "RUBOCOP_COMMIT_SPEC" => options[:"commit-spec"] }, command)
110
+ end
111
+
112
+ desc "ready_for_promotion", "display the next n cops with the fewest offenses"
113
+ method_option "n", type: :numeric, default: 1, desc: "number of cops to display"
114
+ def ready_for_promotion
115
+ finder = Helpers::NextCopForPromotion.new(options[:rubocop_todo_file])
116
+ todo_config = YAML.load_file(options[:rubocop_todo_file])
117
+
118
+ puts "The following cop(s) are ready to be promoted to enforced. Good luck!"
119
+ puts "Remember to run `nucop:regen_backlog` to capture your hard work."
120
+ puts
121
+ finder.find(options["n"].to_i).each do |todo|
122
+ puts "#{todo.name} with #{todo.offenses} offenses:"
123
+ puts
124
+
125
+ files = todo_config.fetch(todo.name, {}).fetch("Exclude", [])
126
+
127
+ system("bundle exec rubocop --parallel --config #{options[:rubocop_todo_config_file]} --only #{todo.name} #{files.join(' ')}")
128
+ puts("*" * 100) if options["n"] > 1
129
+ puts
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ # some cops cannot be used with the --only option and will raise an error
136
+ # this filters them out
137
+ def cops_to_enforce
138
+ cops = enforced_cops
139
+
140
+ cops.delete("Lint/UnneededCopDisableDirective")
141
+
142
+ cops
143
+ end
144
+
145
+ def enforced_cops
146
+ @enforced_cops ||= YAML.load_file(options[:enforced_cops_file])
147
+ end
148
+
149
+ def capture_std_out(command, error_message = nil, stdin_data = nil)
150
+ std_out, std_error, status = Open3.capture3(command, stdin_data: stdin_data)
151
+ print_errors_and_exit(std_error, error_message) unless status.success?
152
+
153
+ std_out
154
+ end
155
+
156
+ def print_errors_and_exit(std_error, message = "An error has occurred")
157
+ warn message
158
+ puts std_error
159
+ puts "Exiting"
160
+ exit 1
161
+ end
162
+
163
+ def print_cops_being_run(only_option)
164
+ if only_option
165
+ enforced_cops_count = Helpers::CopCounter.count(enabled_cops, only_option.split(","))
166
+ puts "Running with a force of #{enforced_cops_count} cops. See '#{options[:enforced_cops_file]}' for more details."
167
+ else
168
+ puts "Running all cops (specify using the 'only' option)"
169
+ end
170
+ end
171
+
172
+ def multi_line_to_single_line(str)
173
+ str.split(/\n+/).join(" ")
174
+ end
175
+
176
+ def pass_through_flag(options, option)
177
+ pass_through_option(options, option, true)
178
+ end
179
+
180
+ def pass_through_option(options, option, is_flag_option = false)
181
+ return nil unless options[option]
182
+ "--#{option} #{options[option] if is_flag_option}"
183
+ end
184
+
185
+ def files_changed_since(commit_spec)
186
+ `git diff #{commit_spec} HEAD --name-only`
187
+ .split("\n")
188
+ .select { |e| e.end_with?(".rb") }
189
+ end
190
+
191
+ def regenerate_rubocop_todos
192
+ puts "Regenerating '#{options[:rubocop_todo_file]}'. Please be patient..."
193
+
194
+ rubocop_options = [
195
+ "--auto-gen-config",
196
+ "--config #{options[:rubocop_todo_config_file]}",
197
+ "--exclude-limit #{options[:'exclude-limit']}",
198
+ "--require rubocop-rspec",
199
+ "--require rubocop-performance",
200
+ "--require rubocop-rails"
201
+ ]
202
+
203
+ rubocop_command = "DISABLE_SPRING=1 bundle exec rubocop #{rubocop_options.join(' ')}"
204
+
205
+ system(rubocop_command)
206
+
207
+ # RuboCop wants to inherit from our todos (options[:rubocop_todo_file]) in our backlog configuration file (options[:rubocop_todo_config_file])
208
+ # However, that means the next time we try to update our backlog, it will NOT include the violations recorded as todo
209
+ # For now, we ignore any changes in our backlog config
210
+ system("git checkout #{options[:rubocop_todo_config_file]}")
211
+ end
212
+
213
+ def update_enforced_cops
214
+ puts "Updating enforced cops list..."
215
+
216
+ current_enforced_cops = Helpers::CopSet.new(enforced_cops)
217
+ cops_without_violations.each do |cop|
218
+ current_enforced_cops.add_cop(cop)
219
+ end
220
+
221
+ if current_enforced_cops.cop_added?
222
+ File.open(options[:enforced_cops_file], "w+") do |f|
223
+ f.write(current_enforced_cops.to_a.sort.to_yaml)
224
+ end
225
+ puts "Updated '#{options[:enforced_cops_file]}'!"
226
+ else
227
+ puts "No new cops are clear of violations"
228
+ end
229
+ end
230
+
231
+ def cops_without_violations
232
+ cops_with_violations = YAML.load_file(options[:rubocop_todo_file]).map(&:first)
233
+
234
+ enabled_cops - cops_with_violations
235
+ end
236
+
237
+ def enabled_cops
238
+ YAML.load(`bundle exec rubocop --parallel --show-cops`).select { |_, config| config["Enabled"] }.map(&:first)
239
+ end
240
+
241
+ # Override Thor's options method to include Nucop's options
242
+ def options
243
+ return @_options if defined?(@_options)
244
+
245
+ original_options = super
246
+ @_options = Thor::CoreExt::HashWithIndifferentAccess.new(
247
+ configuration_options.merge(original_options)
248
+ )
249
+ end
250
+
251
+ def configuration_options
252
+ if File.exist?(CONFIGURATION_FILEPATH)
253
+ default_configuration.merge(YAML.load_file(CONFIGURATION_FILEPATH))
254
+ else
255
+ default_configuration
256
+ end
257
+ end
258
+
259
+ def default_configuration
260
+ {
261
+ enforced_cops_file: ".rubocop.enforced.yml",
262
+ rubocop_todo_file: ".rubocop_todo.yml",
263
+ rubocop_todo_config_file: ".rubocop.backlog.yml",
264
+ diffignore_file: ".nucop_diffignore"
265
+ }
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,43 @@
1
+ module Nucop
2
+ # This cop looks for usages of `FactoryGirl.create`, etc.
3
+ # See FactoryBotHelper::FACTORY_BOT_METHODS constant for a complete list.
4
+ #
5
+ # The factory methods listed are included everywhere, so referencing the constant should rarely be necessary.
6
+ #
7
+ # @example
8
+ #
9
+ # # bad
10
+ #
11
+ # job = FactoryGirl.create(:job, project: project)
12
+ # FactoryGirl.build(:project, code: "Super Project")
13
+ #
14
+ # # good
15
+ #
16
+ # job = create(:job, project: project)
17
+ # build(:project, code: "Super Project")
18
+ class ExplicitFactoryBotUsage < ::RuboCop::Cop::Cop
19
+ include Helpers::FilePathHelper
20
+
21
+ MSG = "Do not explicitly use `%<constant>s` to build objects. The factory method `%<method>s` is globally available."
22
+
23
+ def_node_matcher :explicit_factory_bot_usage, <<~PATTERN
24
+ (send (const nil? {:FactoryGirl :FactoryBot}) {#{Helpers::FactoryBotHelper.factory_bot_methods_pattern}} ...)
25
+ PATTERN
26
+
27
+ def on_send(node)
28
+ explicit_factory_bot_usage(node) do
29
+ add_offense(node, location: :expression, message: format(MSG, constant: node.receiver.const_name, method: node.method_name))
30
+ end
31
+ end
32
+
33
+ def relevant_file?(file)
34
+ acceptance_or_spec_file?(file) && super
35
+ end
36
+
37
+ def autocorrect(node)
38
+ ->(corrector) do
39
+ corrector.replace(node.source_range, node.source.sub(/(?:FactoryGirl|FactoryBot)[.]/, ""))
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ module Nucop
2
+ # This cop looks for core method overrides
3
+ #
4
+ # @example
5
+ #
6
+ # # bad
7
+ #
8
+ # def blank?
9
+ # # ...
10
+ # end
11
+ #
12
+ # # good
13
+ #
14
+ # def anything_other_than_blank?
15
+ # # ...
16
+ # end
17
+ #
18
+ class NoCoreMethodOverrides < ::RuboCop::Cop::Cop
19
+ MSG = "Core method overridden".freeze
20
+
21
+ def_node_matcher :core_methods, <<~PATTERN
22
+ (def ${:present? :blank? :empty?} ...)
23
+ PATTERN
24
+
25
+ def on_def(node)
26
+ core_methods(node) do |method|
27
+ add_offense(node, location: :expression, message: format(MSG, method: method))
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ module Nucop
2
+ # This cop looks for WIP specs
3
+ #
4
+ # @example
5
+ #
6
+ # # bad
7
+ #
8
+ # it "tests some stuff", :wip do
9
+ # # ...
10
+ # end
11
+ #
12
+ # # good
13
+ #
14
+ # it "tests some stuff" do
15
+ # # ...
16
+ # end
17
+ #
18
+ class NoWipSpecs < ::RuboCop::Cop::Cop
19
+ MSG = "WIP spec found".freeze
20
+
21
+ def_node_matcher :wip_it_specs_present?, <<~PATTERN
22
+ (send nil? :it ... (sym :wip))
23
+ PATTERN
24
+
25
+ def_node_matcher :wip_describe_specs_present?, <<~PATTERN
26
+ (send nil? :describe ... (sym :wip))
27
+ PATTERN
28
+
29
+ def on_send(node)
30
+ return unless wip_it_specs_present?(node) || wip_describe_specs_present?(node)
31
+
32
+ add_offense(node)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,42 @@
1
+ module Nucop
2
+ # This cop looks for usages of `ActiveSupport::OrderedHash`
3
+ #
4
+ # Hashes in Ruby (since 1.9) enumerate their keys in the order they are
5
+ # inserted:
6
+ #
7
+ # "Hashes enumerate their values in the order that the corresponding keys were inserted."
8
+ # http://ruby-doc.org/core-2.1.6/Hash.html
9
+ #
10
+ # @example
11
+ #
12
+ # # bad
13
+ #
14
+ # hash = ActiveSupport::OrderedHash.new
15
+ #
16
+ # # good
17
+ #
18
+ # hash = {}
19
+ class OrderedHash < ::RuboCop::Cop::Cop
20
+ MSG = "Ruby hashes after 1.9 enumerate keys in order of insertion"
21
+
22
+ def_node_matcher :ordered_hash_usage, <<~PATTERN
23
+ (send (const (const nil? :ActiveSupport) :OrderedHash) :new)
24
+ PATTERN
25
+
26
+ def on_send(node)
27
+ ordered_hash_usage(node) do
28
+ add_offense(
29
+ node,
30
+ location: :expression,
31
+ message: MSG
32
+ )
33
+ end
34
+ end
35
+
36
+ def autocorrect(node)
37
+ ->(corrector) do
38
+ corrector.replace(node.source_range, "{}")
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,43 @@
1
+ module Nucop
2
+ # This cop checks that a symbol is used when using a Release Toggle name
3
+ #
4
+ # @example
5
+ #
6
+ # # bad
7
+ #
8
+ # release_toggle_enabled?("move_out_of_wip_autocomplete")
9
+ # release_toggle_enabled_for_any_site?("versioned_production_specification_ui")
10
+ # ReleaseToggles.enabled?("test_toggle", site_id: current_user.site_id)
11
+ # ReleaseToggles.disabled?("test_toggl"e, site_id: current_user.site_id)
12
+ # ReleaseToggles.enable("test_toggle", site_id: current_user.site_id)
13
+ # ReleaseToggles.disable!("test_toggle", site_id: current_user.site_id)
14
+ #
15
+ # # good
16
+ #
17
+ # release_toggle_enabled?(:move_out_of_wip_autocomplete)
18
+ #
19
+ class ReleaseTogglesUseSymbols < ::RuboCop::Cop::Cop
20
+ MSG = "Use a symbol when refering to a Release Toggle's by name".freeze
21
+
22
+ def_node_matcher :test_helper?, <<~PATTERN
23
+ (send nil? {:release_toggle_enabled? :release_toggle_enabled_for_any_site?} (str _))
24
+ PATTERN
25
+
26
+ def_node_matcher :release_toggles_public_api_method?, <<~PATTERN
27
+ (send (const nil? :ReleaseToggles) {:enabled? :disabled? :enable :disable :enable! :disable!} (str _) ...)
28
+ PATTERN
29
+
30
+ def on_send(node)
31
+ test_helper?(node) { add_offense(node, message: MSG, location: node.children[2].loc.expression) }
32
+ release_toggles_public_api_method?(node) { add_offense(node, message: MSG, location: node.children[2].loc.expression) }
33
+ end
34
+
35
+ def autocorrect(node)
36
+ ->(corrector) do
37
+ toggle_name = node.children[2].value
38
+
39
+ corrector.replace(node.children[2].source_range, ":#{toggle_name}")
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,37 @@
1
+ module Nucop
2
+ # This cop looks for defined methods in spec files that would shadow methods defined in FactoryBot::Syntax::Methods.
3
+ # See FactoryBotHelper::FACTORY_BOT_METHODS constant for a complete list.
4
+ #
5
+ # @example
6
+ #
7
+ # # bad
8
+ #
9
+ # def create(args)
10
+ # ...
11
+ # end
12
+ #
13
+ # # good
14
+ #
15
+ # def create_transfer_pallet(args)
16
+ # ...
17
+ # end
18
+ class ShadowingFactoryBotCreationMethods < ::RuboCop::Cop::Cop
19
+ include Helpers::FilePathHelper
20
+
21
+ MSG = "Method `%<method>s` shadows a FactoryBot method. Please rename it to be more specific."
22
+
23
+ def_node_matcher :factory_bot_methods, <<~PATTERN
24
+ (def ${#{Helpers::FactoryBotHelper.factory_bot_methods_pattern}} ...)
25
+ PATTERN
26
+
27
+ def on_def(node)
28
+ factory_bot_methods(node) do |method|
29
+ add_offense(node, location: :expression, message: format(MSG, method: method))
30
+ end
31
+ end
32
+
33
+ def relevant_file?(file)
34
+ acceptance_or_spec_file?(file) && super
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,52 @@
1
+ module Nucop
2
+ module Formatters
3
+ class GitDiffFormatter < RuboCop::Formatter::ProgressFormatter
4
+ def initialize(output, options = {})
5
+ super
6
+
7
+ populate_history_from_git
8
+ @offenses_per_file = {}
9
+ end
10
+
11
+ def file_finished(file, offenses)
12
+ return unless file_touched?(file)
13
+
14
+ offenses_in_changes = offenses_from_git_history(file, offenses)
15
+ @offenses_per_file[file] = offenses_in_changes.size
16
+
17
+ # modify parent Formatter to print what we want
18
+ @offenses_for_files[file] = offenses_in_changes if offenses_in_changes.any?
19
+ report_file_as_mark(offenses_in_changes)
20
+ end
21
+
22
+ def finished(_inspected_files)
23
+ @total_offense_count = @offenses_per_file.values.reduce(0, :+)
24
+ @total_correction_count = 0
25
+
26
+ super
27
+ end
28
+
29
+ private
30
+
31
+ def populate_history_from_git
32
+ commit_spec = ENV["RUBOCOP_COMMIT_SPEC"] || "master"
33
+
34
+ diff = `git --no-pager diff #{commit_spec}`
35
+
36
+ @git_history = ::GitDiffParser.parse(diff).each_with_object({}) do |patch, acc|
37
+ next if patch.changed_line_numbers.empty?
38
+
39
+ acc[File.expand_path(patch.file)] = patch.changed_line_numbers
40
+ end
41
+ end
42
+
43
+ def file_touched?(file)
44
+ @git_history.key?(file)
45
+ end
46
+
47
+ def offenses_from_git_history(file, offenses)
48
+ offenses.select { |offense| @git_history[file].include?(offense.line) }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,45 @@
1
+ require "rexml/document"
2
+
3
+ module Nucop
4
+ module Formatters
5
+ class JUnitFormatter < ::RuboCop::Formatter::BaseFormatter
6
+ # This gives all cops - we really want all _enabled_ cops, but
7
+ # that is difficult to obtain - no access to config object here.
8
+ COPS = RuboCop::Cop::Cop.all
9
+
10
+ def started(_target_file)
11
+ @document = REXML::Document.new.tap do |d|
12
+ d << REXML::XMLDecl.new
13
+ end
14
+ @testsuites = REXML::Element.new("testsuites", @document)
15
+ @testsuite = REXML::Element.new("testsuite", @testsuites).tap do |el|
16
+ el.add_attributes("name" => "rubocop")
17
+ end
18
+ end
19
+
20
+ def file_finished(file, offences)
21
+ # One test case per cop per file
22
+ COPS.each do |cop|
23
+ cop_offences = offences.select { |offence| offence.cop_name == cop.cop_name }
24
+ unless cop_offences.empty?
25
+ REXML::Element.new("testcase", @testsuite).tap do |f|
26
+ f.attributes["classname"] = file.gsub(/\.rb\Z/, "").gsub("#{Dir.pwd}/", "").tr("/", ".")
27
+ f.attributes["name"] = "Rubocop: #{cop.cop_name}"
28
+ f.attributes["file"] = cop.cop_name
29
+ cop_offences.each do |offence|
30
+ REXML::Element.new("failure", f).tap do |e|
31
+ e.add_text("#{offence.message}\n\n")
32
+ e.add_text(offence.location.to_s.sub("/usr/src/app/", ""))
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ def finished(_inspected_files)
41
+ @document.write(output, 2)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,51 @@
1
+ # class to count the number of cops from a list of RuboCop "only" options
2
+ # i.e. it accounts for whole "Departments"
3
+ #
4
+ # Examaples:
5
+ # "Style/Blah" is 1 cops
6
+ # "Layout" may represent 70 cops
7
+ module Nucop
8
+ module Helpers
9
+ class CopCounter
10
+ def self.count(all_cops, cops_or_departments)
11
+ new(all_cops).count(cops_or_departments)
12
+ end
13
+
14
+ def initialize(cops)
15
+ @cops_by_department = group_by_department(cops)
16
+ end
17
+
18
+ def count(cops_or_departments)
19
+ cops_or_departments
20
+ .map do |cop_or_department|
21
+ if department?(cop_or_department)
22
+ @cops_by_department.fetch(cop_or_department, []).length
23
+ else
24
+ 1
25
+ end
26
+ end
27
+ .reduce(0, &:+)
28
+ end
29
+
30
+ private
31
+
32
+ def group_by_department(cop_names)
33
+ cop_names.group_by do |cop_name|
34
+ if department?(cop_name)
35
+ raise "Expected fully-qualified cops by name (i.e. Department/Cop). Got: #{cop_name}"
36
+ end
37
+
38
+ department(cop_name)
39
+ end
40
+ end
41
+
42
+ def department?(cop_name)
43
+ !cop_name.include?("/")
44
+ end
45
+
46
+ def department(cop_name)
47
+ cop_name.split("/").first
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,48 @@
1
+ require "set"
2
+
3
+ module Nucop
4
+ module Helpers
5
+ class CopSet
6
+ def initialize(initial_cops = [])
7
+ @cops = Set.new
8
+
9
+ add_cops(initial_cops)
10
+
11
+ @new_cop_added = false
12
+ end
13
+
14
+ def add_cops(cops)
15
+ cops.each(&method(:add_cop))
16
+ end
17
+
18
+ # add a single cop to the set
19
+ # if a cops department is already included,
20
+ # the cop is not added (it is part of the department already)
21
+ def add_cop(cop)
22
+ department = find_department(cop)
23
+
24
+ return if department && @cops.include?(department)
25
+ return if @cops.include?(cop)
26
+
27
+ @cops << cop
28
+ @new_cop_added = true
29
+ end
30
+
31
+ def to_a
32
+ @cops.to_a
33
+ end
34
+
35
+ def cop_added?
36
+ @new_cop_added
37
+ end
38
+
39
+ private
40
+
41
+ def find_department(cop)
42
+ return unless cop.include?("/")
43
+
44
+ cop.split("/").first
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,30 @@
1
+ module Nucop
2
+ module Helpers
3
+ module FactoryBotHelper
4
+ extend self
5
+
6
+ FACTORY_BOT_METHODS = [
7
+ :build,
8
+ :build_list,
9
+ :build_pair,
10
+
11
+ :create,
12
+ :create_list,
13
+ :create_pair,
14
+
15
+ :build_stubbed,
16
+ :build_stubbed_list,
17
+ :build_stubbed_pair,
18
+
19
+ :attributes_for,
20
+ :attributes_for_list,
21
+ :attributes_for_pair
22
+ ]
23
+ private_constant :FACTORY_BOT_METHODS
24
+
25
+ def factory_bot_methods_pattern
26
+ FACTORY_BOT_METHODS.map(&:inspect).join(" ")
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ module Nucop
2
+ module Helpers
3
+ module FilePathHelper
4
+ def acceptance_or_spec_file?(filepath)
5
+ Pathname.new(filepath).fnmatch?(File.join("**", "{spec,acceptance}", "**"), File::FNM_EXTGLOB)
6
+ end
7
+
8
+ def support_file?(filepath)
9
+ Pathname.new(filepath).fnmatch?(File.join("**", "support", "**"), File::FNM_EXTGLOB)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,54 @@
1
+ module Nucop
2
+ module Helpers
3
+ class NextCopForPromotion
4
+ Todo = Struct.new(:name, :offenses)
5
+
6
+ def initialize(todo_filepath)
7
+ @todo_filepath = todo_filepath
8
+ extract_todos
9
+ sort_todos_by_offense_count
10
+ end
11
+
12
+ def find(how_many = 5)
13
+ @todos.take(how_many)
14
+ end
15
+
16
+ private
17
+
18
+ def extract_todos
19
+ @todos =
20
+ extract_offense_counts_and_names
21
+ .each_slice(2)
22
+ .map { |count, name| Todo.new(name, count.to_i) }
23
+ end
24
+
25
+ def extract_offense_counts_and_names
26
+ data = []
27
+
28
+ each_line do |line|
29
+ if (count_match = line.match(/Offense count: (\d+)/))
30
+ data << count_match.captures.first
31
+ end
32
+
33
+ if (name_match = line.match(/^(\w+[\/]\w+):$/))
34
+ data << name_match.captures.first
35
+ end
36
+ end
37
+
38
+ data
39
+ end
40
+
41
+ def each_line
42
+ File.open(@todo_filepath, "r") do |file|
43
+ file.each_line do |line|
44
+ yield line
45
+ end
46
+ end
47
+ end
48
+
49
+ def sort_todos_by_offense_count
50
+ @todos.sort! { |i, j| i.offenses <=> j.offenses }
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,3 @@
1
+ module Nucop
2
+ VERSION = "0.1.2"
3
+ end
@@ -0,0 +1,14 @@
1
+ require "bundler/setup"
2
+ require "nucop"
3
+
4
+ RSpec.configure do |config|
5
+ # Enable flags like --only-failures and --next-failure
6
+ config.example_status_persistence_file_path = ".rspec_status"
7
+
8
+ # Disable RSpec exposing methods globally on `Module` and `main`
9
+ config.disable_monkey_patching!
10
+
11
+ config.expect_with(:rspec) do |c|
12
+ c.syntax = :expect
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,176 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nucop
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Jason Schweier
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-11-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 13.0.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 13.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 3.9.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 3.9.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: git_diff_parser
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.75.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 0.75.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-performance
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 1.5.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 1.5.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
88
+ - !ruby/object:Gem::Version
89
+ version: 2.3.2
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '='
95
+ - !ruby/object:Gem::Version
96
+ version: 2.3.2
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '='
102
+ - !ruby/object:Gem::Version
103
+ version: 1.36.0
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '='
109
+ - !ruby/object:Gem::Version
110
+ version: 1.36.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: ruby-progressbar
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.10'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.10'
125
+ description:
126
+ email:
127
+ - jasons@nulogy.com
128
+ executables:
129
+ - nucop
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - bin/nucop
134
+ - lib/nucop.rb
135
+ - lib/nucop/cli.rb
136
+ - lib/nucop/cops/explicit_factory_bot_usage.rb
137
+ - lib/nucop/cops/no_core_method_overrides.rb
138
+ - lib/nucop/cops/no_wip_specs.rb
139
+ - lib/nucop/cops/ordered_hash.rb
140
+ - lib/nucop/cops/release_toggles_use_symbols.rb
141
+ - lib/nucop/cops/shadowing_factory_bot_creation_methods.rb
142
+ - lib/nucop/formatters/git_diff_formatter.rb
143
+ - lib/nucop/formatters/junit_formatter.rb
144
+ - lib/nucop/helpers/cop_counter.rb
145
+ - lib/nucop/helpers/cop_set.rb
146
+ - lib/nucop/helpers/factory_bot_helper.rb
147
+ - lib/nucop/helpers/file_path_helper.rb
148
+ - lib/nucop/helpers/next_cop_for_promotion.rb
149
+ - lib/nucop/version.rb
150
+ - spec/spec_helper.rb
151
+ homepage:
152
+ licenses: []
153
+ metadata: {}
154
+ post_install_message:
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ requirements: []
169
+ rubyforge_project:
170
+ rubygems_version: 2.7.6
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: Nulogy's implementation of RuboCop, including custom cops and additional
174
+ tooling.
175
+ test_files:
176
+ - spec/spec_helper.rb