nucop 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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