barsoom_utils 0.3.0.79 → 0.3.0.82

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: d567d7a3ae76f80b86ad642cf590526cb3bc2a2be5b95c94abf8944142a40b15
4
- data.tar.gz: 0fd5468990225ed9d0faf43d860b6afe2ccb5b24fb5786077cde7054445ea832
3
+ metadata.gz: 1757860af2eca9a71fffa1fa0c47a42e572314816f4dcf02d752d60c713ace67
4
+ data.tar.gz: 54eb7e03409786d5eb87c34affad0a363b2ac3d17ee19ec22c75c800fd171c6d
5
5
  SHA512:
6
- metadata.gz: ce1a9a7ef20a8f445b8974a3e63b471c1b20ca952f2608e2fb6e35a9f502a8ef961f4d9087dd02cc6e41a6ba9e234df7ba812f6cac42d057d49a3cc00ba02abd
7
- data.tar.gz: 787e53e219778ea27306a49758ce0220fbfa1be5e57743f0b5a14a14fe56288d07f914ae885cacf996e2c02dc9ed35151907457b0a0f1f7e4b2fc2bc818c2483
6
+ metadata.gz: 66619d39cab01f97f5da75ab364f6f5f215f1c7755683518e71fa5d91bd7c3c3689828e0088b2ee562b07cf891a852378e6ee5f1533e4ee6c40103cd28db2f25
7
+ data.tar.gz: ae669cd31bb669333412b0ea3f1bb6ebca0c584ed602685acf750f32106716fddd412ac657335ba7fc5b0ca32aa20bc4a0d11770746bf0ea8eddef37ddc1908c
data/README.md CHANGED
@@ -50,6 +50,8 @@ inherit_gem:
50
50
  - shared_rubocop.yml
51
51
  ```
52
52
 
53
+ `shared_rubocop.yml` also loads our custom cops, in `lib/rubocop/cop/barsoom`.
54
+
53
55
  ## Developing
54
56
 
55
57
  Running tests:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0.79
1
+ 0.3.0.82
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ # Custom cops, loaded by shared_rubocop.yml.
3
+ require "rubocop"
4
+ require_relative "../rubocop/cop/barsoom/attr_extras_initialize"
5
+ require_relative "../rubocop/cop/barsoom/inline_private_class_method"
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+ module RuboCop
3
+ module Cop
4
+ module Barsoom
5
+ # Checks for an `initialize` that only assigns each of its arguments to an instance variable of the same name.
6
+ # attr_extras can define that for you.
7
+ #
8
+ # Positional defaults, splats and block arguments are accepted, since attr_extras has no equivalent. So are
9
+ # keyword defaults other than immutable literals: attr_extras evaluates them once, when the class is loaded.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # def initialize(user, notify:)
14
+ # @user = user
15
+ # @notify = notify
16
+ # end
17
+ #
18
+ # # good
19
+ # pattr_initialize :user, [ :notify! ]
20
+ class AttrExtrasInitialize < Base
21
+ MSG = "Use attr_extras, e.g. `pattr_initialize`, instead of an `initialize` that only assigns its arguments."
22
+ SUPPORTED_ARGUMENT_TYPES = %i[arg kwarg kwoptarg].freeze
23
+ IMMUTABLE_DEFAULT_TYPES = %i[nil true false sym int float].freeze
24
+
25
+ requires_gem "attr_extras"
26
+
27
+ def on_def(node)
28
+ return unless node.method?(:initialize)
29
+ return unless node.each_ancestor(:class, :module, :sclass).first&.class_type?
30
+ return unless supported_arguments?(node.arguments)
31
+ return unless only_assigns?(node.body, node.arguments.map(&:name))
32
+
33
+ add_offense(node.loc.keyword.join(node.loc.name))
34
+ end
35
+
36
+ private
37
+
38
+ def supported_arguments?(arguments)
39
+ arguments.any? && arguments.all? { |argument|
40
+ SUPPORTED_ARGUMENT_TYPES.include?(argument.type) &&
41
+ (!argument.kwoptarg_type? || IMMUTABLE_DEFAULT_TYPES.include?(argument.default_value.type))
42
+ }
43
+ end
44
+
45
+ def only_assigns?(body, names)
46
+ return false unless body
47
+
48
+ statements = body.begin_type? ? body.children : [ body ]
49
+ assigned = statements.map { |statement| assigned_argument(statement) }
50
+
51
+ assigned.all? && assigned.sort == names.sort
52
+ end
53
+
54
+ def assigned_argument(statement)
55
+ return unless statement.ivasgn_type? && statement.expression&.lvar_type?
56
+
57
+ name = statement.expression.children.first
58
+ name if statement.name == :"@#{name}"
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+ module RuboCop
3
+ module Cop
4
+ module Barsoom
5
+ # Checks for `private_class_method` listing class methods by name, when they're defined with `def self.` in the
6
+ # same class body. Putting the modifier before each definition shows at a glance which class methods are private.
7
+ #
8
+ # Names not defined that way, e.g. `private_class_method :new`, are accepted. The offense is only corrected when
9
+ # every listed name is defined in the class body.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # def self.parse(value) = Time.iso8601(value)
14
+ #
15
+ # private_class_method :parse
16
+ #
17
+ # # good
18
+ # private_class_method \
19
+ # def self.parse(value) = Time.iso8601(value)
20
+ class InlinePrivateClassMethod < Base
21
+ include RangeHelp
22
+ extend AutoCorrector
23
+
24
+ MSG = "Use `private_class_method \\` before each `def self.` instead of listing the methods by name."
25
+ RESTRICT_ON_SEND = %i[private_class_method].freeze
26
+
27
+ def on_send(node)
28
+ return if node.receiver || node.arguments.empty? || !node.arguments.all?(&:sym_type?)
29
+
30
+ definitions = class_method_definitions(node.parent)
31
+ names = node.arguments.map(&:value)
32
+ return if names.none? { |name| definitions.key?(name) }
33
+
34
+ add_offense(node) do |corrector|
35
+ next unless names.all? { |name| definitions.key?(name) }
36
+
37
+ names.each do |name|
38
+ definition = definitions.fetch(name)
39
+ corrector.insert_before(definition, "private_class_method \\\n#{" " * definition.loc.column}")
40
+ end
41
+ corrector.remove(removal_range(node))
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def class_method_definitions(body)
48
+ return {} unless body&.begin_type?
49
+
50
+ body.each_child_node(:defs).select { |definition| definition.receiver.self_type? }.to_h { |definition| [ definition.method_name, definition ] }
51
+ end
52
+
53
+ # The whole line, and the blank line above it, so no blank line is left before the class's `end`.
54
+ def removal_range(node)
55
+ range = range_by_whole_lines(node.source_range, include_final_newline: true)
56
+ previous_line = processed_source.lines[node.first_line - 2]
57
+ previous_line&.strip&.empty? ? range.with(begin_pos: range.begin_pos - previous_line.length - 1) : range
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
data/shared_rubocop.yml CHANGED
@@ -1,5 +1,9 @@
1
1
  # Configuration for the Rubocop code linting tool. See README.
2
2
 
3
+ # Our custom cops, in lib/rubocop/cop/barsoom.
4
+ require:
5
+ - barsoom_utils/rubocop
6
+
3
7
  AllCops:
4
8
  ActiveSupportExtensionsEnabled: true
5
9
  DisabledByDefault: true
@@ -381,3 +385,12 @@ Gemspec/RequireMFA:
381
385
  # https://docs.rubocop.org/rubocop/cops_naming.html#namingblockforwarding
382
386
  Naming/BlockForwarding:
383
387
  Enabled: true
388
+
389
+ # Allow e.g. `pattr_initialize :user` and disallow an `initialize` that only assigns `@user = user`.
390
+ # Only runs in projects that have attr_extras in their Gemfile.lock.
391
+ Barsoom/AttrExtrasInitialize:
392
+ Enabled: true
393
+
394
+ # Allow e.g. `private_class_method \` before `def self.parse` and disallow `private_class_method :parse` further down.
395
+ Barsoom/InlinePrivateClassMethod:
396
+ Enabled: true
@@ -7,3 +7,7 @@ Rails/CompactBlank:
7
7
  # Allow e.g. `String#end_with?` and disallow `String#ends_with?`.
8
8
  Rails/ActiveSupportAliases:
9
9
  Enabled: true
10
+
11
+ # Allow e.g. `list.index_with { value(it) }` and disallow `list.to_h { [it, value(it)] }`.
12
+ Rails/IndexWith:
13
+ Enabled: true
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+ require "rubocop_helper"
3
+
4
+ RSpec.describe RuboCop::Cop::Barsoom::AttrExtrasInitialize, :config do
5
+ let(:gem_versions) { { "attr_extras" => "7.1.0" } }
6
+
7
+ it "registers an offense for an initialize that only assigns its positional arguments" do
8
+ expect_offense(<<~RUBY)
9
+ class Foo
10
+ def initialize(bar, baz)
11
+ ^^^^^^^^^^^^^^ Use attr_extras, e.g. `pattr_initialize`, instead of an `initialize` that only assigns its arguments.
12
+ @bar = bar
13
+ @baz = baz
14
+ end
15
+ end
16
+ RUBY
17
+ end
18
+
19
+ it "registers an offense for required keyword arguments" do
20
+ expect_offense(<<~RUBY)
21
+ class Foo
22
+ def initialize(bar, baz:)
23
+ ^^^^^^^^^^^^^^ Use attr_extras, e.g. `pattr_initialize`, instead of an `initialize` that only assigns its arguments.
24
+ @bar = bar
25
+ @baz = baz
26
+ end
27
+ end
28
+ RUBY
29
+ end
30
+
31
+ it "registers an offense for keyword arguments with immutable literal defaults" do
32
+ expect_offense(<<~RUBY)
33
+ class Foo
34
+ def initialize(bar: nil, baz: :qux, limit: 10)
35
+ ^^^^^^^^^^^^^^ Use attr_extras, e.g. `pattr_initialize`, instead of an `initialize` that only assigns its arguments.
36
+ @bar = bar
37
+ @baz = baz
38
+ @limit = limit
39
+ end
40
+ end
41
+ RUBY
42
+ end
43
+
44
+ it "accepts keyword defaults that attr_extras would evaluate once, at load time" do
45
+ expect_no_offenses(<<~RUBY)
46
+ class Foo
47
+ def initialize(now: Time.now, list: [], name: "x")
48
+ @now = now
49
+ @list = list
50
+ @name = name
51
+ end
52
+ end
53
+ RUBY
54
+ end
55
+
56
+ it "accepts positional defaults, which attr_extras doesn't support" do
57
+ expect_no_offenses(<<~RUBY)
58
+ class Foo
59
+ def initialize(bar, baz = nil)
60
+ @bar = bar
61
+ @baz = baz
62
+ end
63
+ end
64
+ RUBY
65
+ end
66
+
67
+ it "accepts splat and block arguments" do
68
+ expect_no_offenses(<<~RUBY)
69
+ class Foo
70
+ def initialize(*bars, **opts, &block)
71
+ @bars = bars
72
+ @opts = opts
73
+ @block = block
74
+ end
75
+ end
76
+ RUBY
77
+ end
78
+
79
+ it "accepts an initialize that does anything besides assigning its arguments" do
80
+ expect_no_offenses(<<~RUBY)
81
+ class Foo
82
+ def initialize(bar, baz)
83
+ @bar = bar
84
+ @baz = baz.to_s
85
+ @qux = []
86
+ end
87
+ end
88
+ RUBY
89
+ end
90
+
91
+ it "accepts an initialize that calls super" do
92
+ expect_no_offenses(<<~RUBY)
93
+ class Foo < Base
94
+ def initialize(bar)
95
+ super()
96
+ @bar = bar
97
+ end
98
+ end
99
+ RUBY
100
+ end
101
+
102
+ it "accepts instance variables named differently from the arguments" do
103
+ expect_no_offenses(<<~RUBY)
104
+ class Foo
105
+ def initialize(bar)
106
+ @baz = bar
107
+ end
108
+ end
109
+ RUBY
110
+ end
111
+
112
+ it "accepts an initialize that leaves an argument unassigned" do
113
+ expect_no_offenses(<<~RUBY)
114
+ class Foo
115
+ def initialize(bar, _baz)
116
+ @bar = bar
117
+ end
118
+ end
119
+ RUBY
120
+ end
121
+
122
+ it "accepts an initialize without arguments" do
123
+ expect_no_offenses(<<~RUBY)
124
+ class Foo
125
+ def initialize
126
+ end
127
+ end
128
+ RUBY
129
+ end
130
+
131
+ it "accepts an initialize in a module" do
132
+ expect_no_offenses(<<~RUBY)
133
+ module Foo
134
+ def initialize(bar)
135
+ @bar = bar
136
+ end
137
+ end
138
+ RUBY
139
+ end
140
+
141
+ context "when the project doesn't use attr_extras" do
142
+ let(:gem_versions) { {} }
143
+
144
+ it "accepts the initialize" do
145
+ expect_no_offenses(<<~RUBY)
146
+ class Foo
147
+ def initialize(bar)
148
+ @bar = bar
149
+ end
150
+ end
151
+ RUBY
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+ require "rubocop_helper"
3
+
4
+ RSpec.describe RuboCop::Cop::Barsoom::InlinePrivateClassMethod, :config do
5
+ let(:ruby_version) { 3.4 }
6
+
7
+ it "registers an offense for listing class methods by name, and moves the modifier onto each definition" do
8
+ expect_offense(<<~RUBY)
9
+ class Foo
10
+ def self.call = parse
11
+
12
+ def self.parse
13
+ :parsed
14
+ end
15
+
16
+ def self.verifier = :verifier
17
+
18
+ private_class_method :parse, :verifier
19
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `private_class_method \\` before each `def self.` instead of listing the methods by name.
20
+ end
21
+ RUBY
22
+
23
+ expect_correction(<<~RUBY)
24
+ class Foo
25
+ def self.call = parse
26
+
27
+ private_class_method \\
28
+ def self.parse
29
+ :parsed
30
+ end
31
+
32
+ private_class_method \\
33
+ def self.verifier = :verifier
34
+ end
35
+ RUBY
36
+ end
37
+
38
+ it "registers an offense without correcting it when some of the names aren't defined in the class body" do
39
+ expect_offense(<<~RUBY)
40
+ class Foo
41
+ def self.build = new
42
+
43
+ def self.parse = :parsed
44
+
45
+ private_class_method :new, :parse
46
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `private_class_method \\` before each `def self.` instead of listing the methods by name.
47
+ end
48
+ RUBY
49
+
50
+ expect_no_corrections
51
+ end
52
+
53
+ it "accepts the modifier before the definition" do
54
+ expect_no_offenses(<<~RUBY)
55
+ class Foo
56
+ private_class_method \\
57
+ def self.parse = :parsed
58
+
59
+ private_class_method def self.verifier = :verifier
60
+ end
61
+ RUBY
62
+ end
63
+
64
+ it "accepts names that aren't defined with `def self.` in the class body" do
65
+ expect_no_offenses(<<~RUBY)
66
+ class Foo
67
+ def self.build = new
68
+
69
+ private_class_method :new
70
+ end
71
+ RUBY
72
+ end
73
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ require "rubocop"
3
+ require "rubocop/rspec/support"
4
+ require "barsoom_utils/rubocop"
5
+
6
+ RSpec.configure do |config|
7
+ config.include RuboCop::RSpec::ExpectOffense
8
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: barsoom_utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0.79
4
+ version: 0.3.0.82
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomas Skogberg
@@ -29,13 +29,19 @@ files:
29
29
  - lib/barsoom_utils.rb
30
30
  - lib/barsoom_utils/exception_notifier.rb
31
31
  - lib/barsoom_utils/feature_toggle.rb
32
+ - lib/barsoom_utils/rubocop.rb
32
33
  - lib/barsoom_utils/spec/debug_helpers.rb
33
34
  - lib/barsoom_utils/version.rb
35
+ - lib/rubocop/cop/barsoom/attr_extras_initialize.rb
36
+ - lib/rubocop/cop/barsoom/inline_private_class_method.rb
34
37
  - script/test
35
38
  - shared_rubocop.yml
36
39
  - shared_rubocop_rails.yml
37
40
  - spec/exception_notifier_spec.rb
38
41
  - spec/feature_toggle_spec.rb
42
+ - spec/rubocop/cop/barsoom/attr_extras_initialize_spec.rb
43
+ - spec/rubocop/cop/barsoom/inline_private_class_method_spec.rb
44
+ - spec/rubocop_helper.rb
39
45
  - spec/spec/debug_helpers_spec.rb
40
46
  - spec/spec_helper.rb
41
47
  homepage: https://dev.auctionet.com/