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 +4 -4
- data/config/default.yml +5 -0
- data/lib/rubocop/cop/style/proc_case_when.rb +140 -0
- data/lib/rubocop/shopify/version.rb +1 -1
- data/lib/rubocop-shopify.rb +1 -0
- data/rubocop.yml +0 -3
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '0885f64f2574e1f3db7bb466fb9dcdbc1c0f1fbaf29336c209e666b10fc9104b'
|
|
4
|
+
data.tar.gz: b0a2e7bbef8f68e98c15348689d1c20433ea2da70c7727146b095028ae22992d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/lib/rubocop-shopify.rb
CHANGED
data/rubocop.yml
CHANGED
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.
|
|
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.
|
|
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: []
|