rubocop-shopify 3.0.1 → 3.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5dbd67ae9f8f0b6a21dc141c0576ceeedad5807e2a9090e8f6703517cb8697cc
4
- data.tar.gz: e2a6435804ceb46bc365409230633d20b16c33d7a3510a06fdf6ea1fde2c0913
3
+ metadata.gz: '0885f64f2574e1f3db7bb466fb9dcdbc1c0f1fbaf29336c209e666b10fc9104b'
4
+ data.tar.gz: b0a2e7bbef8f68e98c15348689d1c20433ea2da70c7727146b095028ae22992d
5
5
  SHA512:
6
- metadata.gz: e36467516497260c536674f8b40e2a09a9d258cb1e25fe99076409a643c2bdc8cad84862eed8c56ba7d0fe6aa5a20488e7ec798631ded20f1a9ba04a06ddce57
7
- data.tar.gz: a0445310d57d1f0cdc85193149c19cd2a5f1c161124c0c546bf1c60187c7778f0b29503bb3c384dac97cce9bf5b74219a47a210c7514b0504dba44cbab31cb35
6
+ metadata.gz: e352f773f31c7846508a1033019a098a7d45f72220a4ca55c81f5be24e1b74e1d5f53f61bbbff03311d62f5c495e7236aa9d8502ad0eb1bac0d4cafceb6fed45
7
+ data.tar.gz: 5c6ca00c17a0a0c465bb2093368fd37fc473a807f6b7db21eaddd57d1aa1aaab4a149304a92f256b2b3abf6500eebf481abaed6d406b5ea464ebd4b085f7a43f
data/config/default.yml CHANGED
@@ -2,3 +2,8 @@ Lint/NoReturnInMemoization:
2
2
  Enabled: true
3
3
  VersionAdded: "3.0.0"
4
4
  Description: "Checks for the use of a `return` with a value in `begin..end` blocks in the context of instance variable assignment such as memoization."
5
+
6
+ Style/ProcCaseWhen:
7
+ Enabled: true
8
+ VersionAdded: "3.0.2"
9
+ Description: "Checks for `case`/`when` where every `when` is a proc or value literal: each inline proc literal allocates a new `Proc` every time the `case` runs and adds `Proc#call` overhead, for what is just a harder-to-read `if`/`elsif` tree."
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop"
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Style
8
+ # Checks for `case` statements whose `when` conditions are only proc/lambda
9
+ # literals and value literals (with at least one proc).
10
+ #
11
+ # Such a `case` is just a harder-to-read `if`/`elsif` tree with a needless
12
+ # performance cost: a `when` clause matches using `pattern === subject`,
13
+ # and for a proc `Proc#===` is an alias for `Proc#call`. Every inline proc
14
+ # literal therefore allocates a brand new `Proc` object each time the
15
+ # `case` is evaluated (on a hot path, once per branch per call) and adds
16
+ # `Proc#call` indirection, where an `if`/`elsif` allocates nothing. For a
17
+ # value literal (number, string, symbol, `nil`, `true`, `false`) `===` is
18
+ # `==`, so the whole statement is exactly equivalent to `if`/`elsif` using
19
+ # `Proc#call` and `==`. Use `if`/`elsif` instead.
20
+ #
21
+ # Constants assigned a proc literal or a value literal *in the same file*
22
+ # are resolved and treated as such. Constants defined in other files cannot
23
+ # be resolved: a bare `when SOME_CONST` is indistinguishable from matching
24
+ # against a class, so those are left alone to avoid flagging idiomatic
25
+ # `case obj when SomeClass`.
26
+ #
27
+ # Cases that use class, range, or regexp patterns are also left alone:
28
+ # those rely on `===` in ways that read far worse as `if` conditions
29
+ # (`is_a?`, `cover?`, `match?`), which is exactly what `case` is for.
30
+ #
31
+ # @example
32
+ #
33
+ # # bad - every `when` is a proc (a new proc is allocated on each call)
34
+ # case value
35
+ # when ->(x) { x > 10 } then :big
36
+ # when ->(x) { x < 0 } then :negative
37
+ # else :other
38
+ # end
39
+ #
40
+ # # bad - only procs and value literals (including value-literal constants)
41
+ # NAME = "widget"
42
+ # case value
43
+ # when NAME then :named
44
+ # when ->(x) { x > 10 } then :big
45
+ # end
46
+ #
47
+ # # good - no proc allocation, and easier to read
48
+ # if value > 10
49
+ # :big
50
+ # elsif value < 0
51
+ # :negative
52
+ # else
53
+ # :other
54
+ # end
55
+ #
56
+ # # good - a class/range/regexp pattern makes `case` the clearer choice,
57
+ # # even alongside a proc.
58
+ # case value
59
+ # when Integer then :int
60
+ # when ->(x) { x > 10 } then :big
61
+ # end
62
+ class ProcCaseWhen < ::RuboCop::Cop::Base
63
+ MSG = "Avoid a `case`/`when` where every `when` is a proc or value " \
64
+ "literal: each proc literal allocates a new `Proc` every time the " \
65
+ "`case` is evaluated and adds `Proc#call` overhead, and the whole " \
66
+ "thing is just a harder-to-read `if`/`elsif` tree. Use `if`/`elsif` " \
67
+ "instead."
68
+
69
+ # Matches `->(x) {}`, `lambda {}`, `proc {}` and `Proc.new {}`, including
70
+ # their numbered-parameter (`_1`) block variants.
71
+ # @!method proc_literal?(node)
72
+ def_node_matcher :proc_literal?, <<~PATTERN
73
+ {
74
+ ({block numblock} (send nil? {:lambda :proc}) ...)
75
+ ({block numblock} (send (const {nil? cbase} :Proc) :new) ...)
76
+ }
77
+ PATTERN
78
+
79
+ def on_new_investigation
80
+ super
81
+ @constant_kinds = collect_constant_kinds
82
+ end
83
+
84
+ def on_case(node)
85
+ # `case` without a subject evaluates each `when` for truthiness rather
86
+ # than with `===`, so procs there are not used as matchers.
87
+ return unless node.condition
88
+
89
+ kinds = node.when_branches.flat_map(&:conditions).map { |condition| kind_of(condition) }
90
+ # Require at least one proc; a case of only value literals is a fine
91
+ # dispatch and out of scope here.
92
+ return unless kinds.include?(:proc)
93
+ # Bail out if any condition is something other than a proc or a value
94
+ # literal (e.g. a class, range, regexp, or an unresolvable constant),
95
+ # where `case` reads better than the equivalent `if`.
96
+ return unless kinds.all? { |kind| kind == :proc || kind == :value }
97
+
98
+ add_offense(node)
99
+ end
100
+
101
+ private
102
+
103
+ # Classifies a `when` condition (or a constant's assigned value) as a
104
+ # proc, a value literal, or `:other` (anything we should not rewrite).
105
+ def kind_of(node)
106
+ # Unwrap a trailing `.freeze` so `FOO = "bar".freeze` counts as a value.
107
+ node = node.receiver if node.send_type? && node.method?(:freeze) && node.receiver
108
+
109
+ if proc_literal?(node)
110
+ :proc
111
+ elsif node.basic_literal?
112
+ :value
113
+ elsif node.const_type?
114
+ # `@constant_kinds` is still nil while being built; an unresolved
115
+ # constant is treated as `:other` (conservative).
116
+ (@constant_kinds || {}).fetch(node.short_name, :other)
117
+ else
118
+ :other
119
+ end
120
+ end
121
+
122
+ # Maps the short (demodulized) name of every constant assigned in this
123
+ # file to its kind, so bare `when CONST` references can be resolved.
124
+ def collect_constant_kinds
125
+ kinds = {}
126
+ ast = processed_source.ast
127
+ return kinds unless ast
128
+
129
+ ast.each_node(:casgn) do |casgn|
130
+ value = casgn.children[2]
131
+ next unless value
132
+
133
+ kinds[casgn.name] = kind_of(value)
134
+ end
135
+ kinds
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Shopify
5
- VERSION = "3.0.1"
5
+ VERSION = "3.0.2"
6
6
  end
7
7
  end
@@ -4,3 +4,4 @@ require "rubocop/shopify/version"
4
4
  require "rubocop/shopify/plugin"
5
5
 
6
6
  require "rubocop/cop/lint/no_return_in_memoization"
7
+ require "rubocop/cop/style/proc_case_when"
data/rubocop.yml CHANGED
@@ -1196,9 +1196,6 @@ Style/RedundantFilterChain:
1196
1196
  Style/RedundantFormat:
1197
1197
  Enabled: false
1198
1198
 
1199
- Style/RedundantFreeze:
1200
- Enabled: false
1201
-
1202
1199
  Style/RedundantHeredocDelimiterQuotes:
1203
1200
  Enabled: false
1204
1201
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-shopify
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.1
4
+ version: 3.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify Engineering
@@ -55,6 +55,7 @@ files:
55
55
  - config/default.yml
56
56
  - lib/rubocop-shopify.rb
57
57
  - lib/rubocop/cop/lint/no_return_in_memoization.rb
58
+ - lib/rubocop/cop/style/proc_case_when.rb
58
59
  - lib/rubocop/shopify/plugin.rb
59
60
  - lib/rubocop/shopify/version.rb
60
61
  - lib/tasks/config.rake
@@ -63,7 +64,7 @@ homepage: https://shopify.github.io/ruby-style-guide/
63
64
  licenses:
64
65
  - MIT
65
66
  metadata:
66
- source_code_uri: https://github.com/Shopify/ruby-style-guide/tree/v3.0.1
67
+ source_code_uri: https://github.com/Shopify/ruby-style-guide/tree/v3.0.2
67
68
  allowed_push_host: https://rubygems.org
68
69
  default_lint_roller_plugin: RuboCop::Shopify::Plugin
69
70
  rdoc_options: []