rubocop-packs 0.0.26 → 0.0.28

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b087fdd5a5ae2a9a51a04d24966313c19a2892153d656beac60bdb15e959726e
4
- data.tar.gz: e4ff803bc9a2a37dd68867da260df1f68c7d8adc666a9cb141dcc95a6ac1e448
3
+ metadata.gz: e4888c0b36e59d1da1aaa62d1390da897fce811909d99c5e1189bca0efd36050
4
+ data.tar.gz: 7d1957e8c598a6acbcfa3b0b8f49578dcb3424d01c20178c2045459654a2478f
5
5
  SHA512:
6
- metadata.gz: 234d22b504eb15892fd02bdab4783e475ab12ec0b6024f5d36519aca0efc1feaa5ac8616f8ba8f242086e5ac100d76edd282c282045136df26b5035954f6306d
7
- data.tar.gz: 56056baf7a26ab3c2bc21038de1f34e418d01d2f0851fe1eb1234d7ec63e069a930505cf8e858fe6ac913ba5556b8ec230a024efbd7e97b0743756f3fac90c3f
6
+ metadata.gz: b55558140df2b0d0e68f64668a94e814c30a6ac42b144d0dc7fbc7118bdf982fb87b45085592ca219946ae6a2df73bb22562a83ae888fb021343ec0cea7c685c
7
+ data.tar.gz: fa54dc58358e0f32104c2acfa0536ff6239430c287a75e5ad0a346ae9979360f59a67037a34599e98a1df525cbe6c31c7350af95b6c33dba1dcdacb02cbdfe88
data/config/default.yml CHANGED
@@ -5,6 +5,7 @@ Packs/ClassMethodsAsPublicApis:
5
5
  - T::Struct
6
6
  - Struct
7
7
  - OpenStruct
8
+ AcceptableMixins: []
8
9
  FailureMode: default
9
10
 
10
11
  Packs/RootNamespaceIsPackName:
@@ -10,6 +10,7 @@ module RuboCop
10
10
  # Options:
11
11
  #
12
12
  # * `AcceptableParentClasses`: A list of classes that, if inherited from, non-class methods are permitted (useful when value objects are a part of your public API)
13
+ # * `AcceptableMixins`: A list of modules that, if included, non-class methods are permitted
13
14
  #
14
15
  # @example
15
16
  #
@@ -47,11 +48,14 @@ module RuboCop
47
48
 
48
49
  acceptable_parent_classes = cop_config['AcceptableParentClasses'] || []
49
50
 
50
- # Used this PR as inspiration to check if we're within a `class << self` block
51
51
  uses_implicit_static_methods = node.each_ancestor(:sclass).first&.identifier&.source == 'self'
52
52
  class_is_allowed_to_have_instance_methods = acceptable_parent_classes.include?(parent_class&.const_name)
53
53
  return if uses_implicit_static_methods || class_is_allowed_to_have_instance_methods
54
54
 
55
+ is_sorbet_interface_or_abstract_class = !module_node.nil? && module_node.descendants.any? { |d| d.is_a?(RuboCop::AST::SendNode) && (d.method_name == :interface! || d.method_name == :abstract!) }
56
+ return if is_sorbet_interface_or_abstract_class
57
+ return if node_includes_acceptable_mixin?(class_node || module_node)
58
+
55
59
  add_offense(
56
60
  node.source_range,
57
61
  message: format(
@@ -59,6 +63,22 @@ module RuboCop
59
63
  )
60
64
  )
61
65
  end
66
+
67
+ private
68
+
69
+ sig { params(node: T.untyped).returns(T::Boolean) }
70
+ def node_includes_acceptable_mixin?(node)
71
+ acceptable_mixins = cop_config['AcceptableMixins'] || []
72
+ return false if node.nil?
73
+
74
+ node.descendants.any? do |d|
75
+ d.is_a?(RuboCop::AST::SendNode) &&
76
+ d.method_name == :include &&
77
+ d.arguments.count == 1 &&
78
+ d.arguments.first.is_a?(RuboCop::AST::ConstNode) &&
79
+ acceptable_mixins.include?(d.arguments.first.const_name)
80
+ end
81
+ end
62
82
  end
63
83
  end
64
84
  end
@@ -148,20 +148,22 @@ module RuboCop
148
148
  errors
149
149
  end
150
150
 
151
- sig { params(args: T.untyped).returns(String) }
151
+ sig { params(args: T.untyped).void }
152
152
  def self.execute_rubocop(args)
153
- with_captured_stdout do
154
- RuboCop::CLI.new.run(args)
155
- end
153
+ RuboCop::CLI.new.run(args)
156
154
  end
157
155
 
158
156
  sig { params(paths: T::Array[String], cop_names: T::Array[String]).returns(T::Array[Offense]) }
159
157
  def self.offenses_for(paths:, cop_names:)
160
158
  cop_arguments = cop_names.join(',')
161
159
  # I think we can potentially use `RuboCop::CLI.new(args)` for this to avoid shelling out and starting another process that needs to reload the bundle
162
- args = [*paths, "--only=#{cop_arguments}", '--format=json']
160
+ args = [*paths, "--only=#{cop_arguments}", '--format=json', '--out=tmp/rubocop-output']
161
+ FileUtils.mkdir_p('tmp')
163
162
  puts "Executing: bundle exec rubocop #{args.join(' ')}"
164
- json = JSON.parse(Private.execute_rubocop(args))
163
+ Private.execute_rubocop(args)
164
+ output = Pathname.new('tmp/rubocop-output')
165
+ json = JSON.parse(Pathname.new('tmp/rubocop-output').read)
166
+ output.delete
165
167
  offenses = T.let([], T::Array[Offense])
166
168
  json['files'].each do |file_hash|
167
169
  filepath = file_hash['path']
@@ -175,16 +177,6 @@ module RuboCop
175
177
 
176
178
  offenses
177
179
  end
178
-
179
- sig { params(block: T.untyped).returns(String) }
180
- def self.with_captured_stdout(&block)
181
- original_stdout = $stdout # capture previous value of $stdout
182
- $stdout = StringIO.new # assign a string buffer to $stdout
183
- yield # perform the body of the user code
184
- $stdout.string # return the contents of the string buffer
185
- ensure
186
- $stdout = original_stdout # restore $stdout to its previous value
187
- end
188
180
  end
189
181
 
190
182
  private_constant :Private
data/lib/rubocop/packs.rb CHANGED
@@ -28,6 +28,12 @@ module RuboCop
28
28
  #
29
29
  sig { params(packs: T::Array[ParsePackwerk::Package], files: T::Array[String]).void }
30
30
  def self.regenerate_todo(packs: [], files: [])
31
+ # Delete the old pack-level rubocop todo files so that we can regenerate the new one from scratch
32
+ packs.each do |pack|
33
+ rubocop_todo_yml = pack.directory.join(PACK_LEVEL_RUBOCOP_TODO_YML)
34
+ rubocop_todo_yml.delete if rubocop_todo_yml.exist?
35
+ end
36
+
31
37
  paths = packs.empty? ? files : packs.map(&:name).reject { |name| name == ParsePackwerk::ROOT_PACKAGE_NAME }
32
38
  offenses = Private.offenses_for(
33
39
  paths: paths,
@@ -39,19 +45,17 @@ module RuboCop
39
45
  next if !pack.directory.join(PACK_LEVEL_RUBOCOP_YML).exist?
40
46
 
41
47
  rubocop_todo_yml = pack.directory.join(PACK_LEVEL_RUBOCOP_TODO_YML)
42
- # If the user is passing in packs, then regenerate from scratch.
43
- if packs.any? && rubocop_todo_yml.exist?
44
- rubocop_todo_yml.delete
45
- rubocop_todo = {}
46
- elsif rubocop_todo_yml.exist?
48
+ if rubocop_todo_yml.exist?
47
49
  rubocop_todo = YAML.load_file(rubocop_todo_yml)
48
50
  else
49
51
  rubocop_todo = {}
50
52
  end
51
53
 
52
- offenses_for_pack.each do |offense|
53
- rubocop_todo[offense.cop_name] ||= { 'Exclude' => [] }
54
- rubocop_todo[offense.cop_name]['Exclude'] << offense.filepath
54
+ offenses_for_pack.group_by(&:filepath).each do |filepath, offenses_by_filepath|
55
+ offenses_by_filepath.map(&:cop_name).uniq.each do |cop_name|
56
+ rubocop_todo[cop_name] ||= { 'Exclude' => [] }
57
+ rubocop_todo[cop_name]['Exclude'] << filepath
58
+ end
55
59
  end
56
60
 
57
61
  next if rubocop_todo.empty?
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-packs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.26
4
+ version: 0.0.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gusto Engineers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-19 00:00:00.000000000 Z
11
+ date: 2022-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -178,7 +178,7 @@ dependencies:
178
178
  - - ">="
179
179
  - !ruby/object:Gem::Version
180
180
  version: '0'
181
- description: Fill this out!
181
+ description: A collection of Rubocop rules for gradually modularizing a ruby codebase
182
182
  email:
183
183
  - dev@gusto.com
184
184
  executables: []
@@ -230,5 +230,5 @@ requirements: []
230
230
  rubygems_version: 3.1.6
231
231
  signing_key:
232
232
  specification_version: 4
233
- summary: Fill this out!
233
+ summary: A collection of Rubocop rules for gradually modularizing a ruby codebase
234
234
  test_files: []