rubocop-instance_variable_access 1.0.0
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 +7 -0
- data/CHANGELOG.md +7 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +136 -0
- data/config/default.yml +4 -0
- data/lib/rubocop/cop/instance_variable_access_cops.rb +3 -0
- data/lib/rubocop/cop/style/instance_variable_access.rb +346 -0
- data/lib/rubocop/instance_variable_access/plugin.rb +36 -0
- data/lib/rubocop/instance_variable_access/version.rb +7 -0
- data/lib/rubocop/instance_variable_access.rb +9 -0
- data/lib/rubocop-instance_variable_access.rb +8 -0
- data/sig/rubocop/cop/instance_variable_access_cops.rbs +2 -0
- data/sig/rubocop/cop/style/instance_variable_access.rbs +249 -0
- data/sig/rubocop/instance_variable_access/plugin.rbs +16 -0
- data/sig/rubocop/instance_variable_access/version.rbs +7 -0
- data/sig/rubocop/instance_variable_access.rbs +8 -0
- data/sig/rubocop-instance_variable_access.rbs +2 -0
- metadata +101 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 944fafee55d6eae0c082e77a20ee605aee7ba1e35aa67e0b54c948d14c874bb9
|
|
4
|
+
data.tar.gz: dff9f511c5db8e20be45eb2dc6a14db0dd533590cd30f7fd62477e157643c5e9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 85fd2668242673960b7ce52680d0f9ed788e8866dbe528c56b2ba6f89a83f0e6c234ebd7a15a6b45020355a573f7b1d802888677dd504175783867340dd7017e
|
|
7
|
+
data.tar.gz: cc2b85b5b008dfe3cbf8422f91c3725a0eee420790c091f1538943e8ac0ff17ee389f3ad61ff3193e1892cdab4167f059e052b40fc0102e3a0733d66258ff528
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
"rubocop-instance_variable_access" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behaviour within this project, please contact us at ["i.tkomiya@gmail.com"](mailto:"i.tkomiya@gmail.com").
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Takeshi KOMIYA
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# RuboCop::InstanceVariableAccess
|
|
2
|
+
|
|
3
|
+
A RuboCop extension that provides `Style/InstanceVariableAccess`, a cop that
|
|
4
|
+
enforces accessing instance variables (and class instance variables) through
|
|
5
|
+
reader methods, even from inside the class that defines them.
|
|
6
|
+
|
|
7
|
+
## Why?
|
|
8
|
+
|
|
9
|
+
Wrapping instance variables in reader methods, even inside the class that
|
|
10
|
+
defines them, follows the
|
|
11
|
+
[Barewords Pattern](https://www.alchemists.io/articles/barewords_pattern):
|
|
12
|
+
|
|
13
|
+
- A typo in a method name raises `NameError` immediately, while a typo in
|
|
14
|
+
an instance variable name (`@nmae`) silently returns `nil`.
|
|
15
|
+
- A reader is easier to extend later (add memoization, a default value, or
|
|
16
|
+
validation) without touching every call site.
|
|
17
|
+
- Ruby's own hash value omission syntax (`{x:}`) ended up supporting
|
|
18
|
+
either a local variable or a method like `attr_reader`.
|
|
19
|
+
|
|
20
|
+
This does mean a reader becomes part of the class's public API unless it is
|
|
21
|
+
`private`; this cop doesn't care about visibility, so keep a reader private
|
|
22
|
+
when the attribute isn't meant to be exposed outside the class.
|
|
23
|
+
|
|
24
|
+
Further reading:
|
|
25
|
+
|
|
26
|
+
- [Barewords Pattern](https://www.alchemists.io/articles/barewords_pattern)
|
|
27
|
+
- [Feature #14579 - Hash value omission](https://bugs.ruby-lang.org/issues/14579)
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
Install the gem:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
bundle add rubocop-instance_variable_access --group development,test --require false
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
Add the following to your `.rubocop.yml`:
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
plugins:
|
|
43
|
+
- rubocop-instance_variable_access
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## The cop
|
|
47
|
+
|
|
48
|
+
`Style/InstanceVariableAccess` flags:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
# bad
|
|
52
|
+
class Person
|
|
53
|
+
def full_name
|
|
54
|
+
"#{@first_name} #{@last_name}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
and suggests:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
# good
|
|
63
|
+
class Person
|
|
64
|
+
attr_reader :first_name, :last_name
|
|
65
|
+
|
|
66
|
+
def full_name
|
|
67
|
+
"#{first_name} #{last_name}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Writing to an instance variable is always allowed, so a memoization idiom
|
|
73
|
+
such as `@memo ||= expensive_call` is unaffected. Reading that same
|
|
74
|
+
instance variable anywhere else while computing the value being assigned
|
|
75
|
+
to it is allowed too, whether the assignment is a plain `=`
|
|
76
|
+
(`@count = @count + 1`, `@count = compute(@count)`) or a compound
|
|
77
|
+
assignment that reads it again explicitly (`@count += compute(@count)`).
|
|
78
|
+
|
|
79
|
+
A method whose name matches the instance variable (`def first_name; ...;
|
|
80
|
+
@first_name; end`) is treated as that variable's own reader, so referencing
|
|
81
|
+
it there is not flagged either, no matter what else the method does first.
|
|
82
|
+
Class instance variables (e.g. `@total` inside `def self.total` or
|
|
83
|
+
`class << self`) are checked the same way as regular instance variables.
|
|
84
|
+
|
|
85
|
+
An instance variable read inside a block passed to `instance_eval`,
|
|
86
|
+
`instance_exec`, `class_eval`, or `module_eval` on anything other than
|
|
87
|
+
`self` isn't flagged, since `self` (and therefore whose instance variable
|
|
88
|
+
is actually being read) changes inside that block:
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
# good: `@first_name` belongs to `other`, not to `Person`
|
|
92
|
+
class Person
|
|
93
|
+
def borrow_name_from(other)
|
|
94
|
+
other.instance_eval { @first_name }
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Autocorrection only rewrites `@foo` to `foo` when an `attr_reader`/
|
|
100
|
+
`attr_accessor` for `foo` already exists in the same class body; a
|
|
101
|
+
hand-written reader doesn't count, since there's no way to tell whether
|
|
102
|
+
it's safe to call in place of `@foo` (it could have side effects, or
|
|
103
|
+
require arguments). Otherwise, only the offense is reported, since
|
|
104
|
+
generating an `attr_reader` automatically could unintentionally widen the
|
|
105
|
+
method's visibility.
|
|
106
|
+
|
|
107
|
+
## Development
|
|
108
|
+
|
|
109
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then,
|
|
110
|
+
run `rake spec` to run the tests. You can also run `bin/console` for an
|
|
111
|
+
interactive prompt that will allow you to experiment.
|
|
112
|
+
|
|
113
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
114
|
+
To release a new version, update the version number in `version.rb` and
|
|
115
|
+
push it to `main`; the [release workflow](.github/workflows/release.yml)
|
|
116
|
+
then publishes the gem to [rubygems.org](https://rubygems.org) automatically.
|
|
117
|
+
|
|
118
|
+
## Contributing
|
|
119
|
+
|
|
120
|
+
Bug reports and pull requests are welcome on GitHub at
|
|
121
|
+
https://github.com/tk0miya/rubocop-instance_variable_access. This project is
|
|
122
|
+
intended to be a safe, welcoming space for collaboration, and contributors
|
|
123
|
+
are expected to adhere to the
|
|
124
|
+
[code of conduct](https://github.com/tk0miya/rubocop-instance_variable_access/blob/main/CODE_OF_CONDUCT.md).
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
The gem is available as open source under the terms of the
|
|
129
|
+
[MIT License](https://opensource.org/licenses/MIT).
|
|
130
|
+
|
|
131
|
+
## Code of Conduct
|
|
132
|
+
|
|
133
|
+
Everyone interacting in the RuboCop::InstanceVariableAccess project's
|
|
134
|
+
codebases, issue trackers, chat rooms and mailing lists is expected to
|
|
135
|
+
follow the
|
|
136
|
+
[code of conduct](https://github.com/tk0miya/rubocop-instance_variable_access/blob/main/CODE_OF_CONDUCT.md).
|
data/config/default.yml
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Style
|
|
6
|
+
# Checks that instance variables (including class instance variables) are
|
|
7
|
+
# accessed through reader methods rather than referenced directly, even
|
|
8
|
+
# from within the class that defines them.
|
|
9
|
+
#
|
|
10
|
+
# Writing to an instance variable (via `=`, `+=`, `||=`, `&&=`, or
|
|
11
|
+
# multiple assignment) is always allowed, so a memoization idiom such
|
|
12
|
+
# as `@memo ||= expensive_call` is unaffected, since it writes to the
|
|
13
|
+
# variable rather than reading it. Reading that same instance variable
|
|
14
|
+
# anywhere else while computing the value being assigned to it is
|
|
15
|
+
# allowed too, whether the assignment is a plain `=`
|
|
16
|
+
# (`@count = @count + 1`, `@count = compute(@count)`) or a compound
|
|
17
|
+
# assignment that reads it again explicitly
|
|
18
|
+
# (`@count += compute(@count)`).
|
|
19
|
+
#
|
|
20
|
+
# A method whose name matches the instance variable (`def name; ...;
|
|
21
|
+
# @name; end`, or its class-method equivalent `def self.name; ...;
|
|
22
|
+
# @name; end`) is treated as that variable's own reader, so referencing
|
|
23
|
+
# it there is not itself an offense, no matter what else the method's
|
|
24
|
+
# body does.
|
|
25
|
+
#
|
|
26
|
+
# An instance variable read inside a block passed to `instance_eval`,
|
|
27
|
+
# `instance_exec`, `class_eval`, or `module_eval` on anything other
|
|
28
|
+
# than `self` is not flagged, since `self` (and therefore whose
|
|
29
|
+
# instance variable is actually being read) changes inside that
|
|
30
|
+
# block.
|
|
31
|
+
#
|
|
32
|
+
# @safety
|
|
33
|
+
# Autocorrection only rewrites `@foo` to `foo` when an `attr_reader`/
|
|
34
|
+
# `attr_accessor` for the same instance/singleton context already
|
|
35
|
+
# exists in the same class body. A hand-written reader like the one
|
|
36
|
+
# above doesn't count, because there is no way to tell whether it is
|
|
37
|
+
# safe to call in place of `@foo` (it could have side effects, or
|
|
38
|
+
# require arguments). Otherwise, only the offense is reported; this
|
|
39
|
+
# cop never generates an `attr_reader`.
|
|
40
|
+
#
|
|
41
|
+
# @example
|
|
42
|
+
# # bad
|
|
43
|
+
# class Person
|
|
44
|
+
# def full_name
|
|
45
|
+
# "#{@first_name} #{@last_name}"
|
|
46
|
+
# end
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
# # good
|
|
50
|
+
# class Person
|
|
51
|
+
# attr_reader :first_name, :last_name
|
|
52
|
+
#
|
|
53
|
+
# def full_name
|
|
54
|
+
# "#{first_name} #{last_name}"
|
|
55
|
+
# end
|
|
56
|
+
# end
|
|
57
|
+
#
|
|
58
|
+
# # good (the reader definition itself is not an offense)
|
|
59
|
+
# class Person
|
|
60
|
+
# private
|
|
61
|
+
#
|
|
62
|
+
# def first_name
|
|
63
|
+
# @first_name
|
|
64
|
+
# end
|
|
65
|
+
# end
|
|
66
|
+
#
|
|
67
|
+
# # good (still just the reader, whatever else it does first)
|
|
68
|
+
# class Person
|
|
69
|
+
# def first_name
|
|
70
|
+
# logger.debug("first_name accessed")
|
|
71
|
+
# @first_name
|
|
72
|
+
# end
|
|
73
|
+
# end
|
|
74
|
+
#
|
|
75
|
+
# # good (memoization writes to the instance variable rather than reading it)
|
|
76
|
+
# class Person
|
|
77
|
+
# def first_name
|
|
78
|
+
# @first_name ||= compute_first_name
|
|
79
|
+
# end
|
|
80
|
+
# end
|
|
81
|
+
#
|
|
82
|
+
# # good (reading `@count` anywhere while rewriting it is fine, `=` or `+=`)
|
|
83
|
+
# class Counter
|
|
84
|
+
# def increment
|
|
85
|
+
# @count = compute(@count)
|
|
86
|
+
# @count += compute(@count)
|
|
87
|
+
# end
|
|
88
|
+
# end
|
|
89
|
+
#
|
|
90
|
+
# # good (`self` changes inside the block, so this isn't Person's own ivar)
|
|
91
|
+
# class Person
|
|
92
|
+
# def borrow_name_from(other)
|
|
93
|
+
# other.instance_eval { @first_name }
|
|
94
|
+
# end
|
|
95
|
+
# end
|
|
96
|
+
class InstanceVariableAccess < Base
|
|
97
|
+
extend AutoCorrector
|
|
98
|
+
|
|
99
|
+
MSG = "Use a reader method instead of directly accessing `%<ivar>s`."
|
|
100
|
+
|
|
101
|
+
RESTRICT_ON_SEND = %i[attr_reader attr_accessor].freeze
|
|
102
|
+
|
|
103
|
+
# Methods that run their block with `self` rebound to something other
|
|
104
|
+
# than the block's lexical `self`, so an instance variable read inside
|
|
105
|
+
# such a block cannot be attributed to the enclosing class/module.
|
|
106
|
+
SELF_CHANGING_BLOCK_METHODS = %i[instance_eval instance_exec class_eval module_eval].freeze
|
|
107
|
+
|
|
108
|
+
# @rbs!
|
|
109
|
+
# def attr_reader_or_accessor?: (RuboCop::AST::Node node) -> bool
|
|
110
|
+
|
|
111
|
+
def_node_matcher :attr_reader_or_accessor?, <<~PATTERN
|
|
112
|
+
(send nil? {:attr_reader :attr_accessor} ...)
|
|
113
|
+
PATTERN
|
|
114
|
+
|
|
115
|
+
# A candidate offense: an instance variable read that isn't inside the
|
|
116
|
+
# body of a method of the same name. Resolved against the enclosing
|
|
117
|
+
# scope's readers once the whole class/module body has been seen.
|
|
118
|
+
Violation = Data.define(
|
|
119
|
+
:node, #: RuboCop::AST::Node -- the instance variable node that may be an offense
|
|
120
|
+
:singleton #: bool -- whether it's in a singleton (class-level) context
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# The readers and pending violations for a single class/module body
|
|
124
|
+
# (or the top level, for the root scope).
|
|
125
|
+
Scope = Struct.new(
|
|
126
|
+
:instance_readers, #: Hash[Symbol, Symbol] -- known instance-level readers, by variable name
|
|
127
|
+
:class_readers, #: Hash[Symbol, Symbol] -- known class-level (singleton) readers, by variable name
|
|
128
|
+
:violations, #: Array[Violation] -- offense candidates collected so far in this scope
|
|
129
|
+
:singleton_depth, #: Integer -- nesting depth inside `class << self` blocks
|
|
130
|
+
keyword_init: true
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def on_new_investigation #: void
|
|
134
|
+
@scope_stack = []
|
|
135
|
+
push_scope
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def on_investigation_end #: void
|
|
139
|
+
report_violations
|
|
140
|
+
pop_scope
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def on_class(_node) #: void
|
|
144
|
+
push_scope
|
|
145
|
+
end
|
|
146
|
+
alias on_module on_class
|
|
147
|
+
|
|
148
|
+
# @rbs _node: RuboCop::AST::Node
|
|
149
|
+
def after_class(_node) #: void
|
|
150
|
+
report_violations
|
|
151
|
+
pop_scope
|
|
152
|
+
end
|
|
153
|
+
alias after_module after_class
|
|
154
|
+
|
|
155
|
+
# @rbs _node: RuboCop::AST::Node
|
|
156
|
+
def on_sclass(_node) #: void
|
|
157
|
+
current_scope.singleton_depth += 1
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# @rbs _node: RuboCop::AST::Node
|
|
161
|
+
def after_sclass(_node) #: void
|
|
162
|
+
current_scope.singleton_depth -= 1
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# @rbs node: RuboCop::AST::SendNode
|
|
166
|
+
def on_send(node) #: void
|
|
167
|
+
return if top_level?
|
|
168
|
+
return unless attr_reader_or_accessor?(node)
|
|
169
|
+
|
|
170
|
+
registry = singleton_context? ? current_scope.class_readers : current_scope.instance_readers
|
|
171
|
+
node.arguments.each do |arg|
|
|
172
|
+
case arg
|
|
173
|
+
when RuboCop::AST::SymbolNode, RuboCop::AST::StrNode
|
|
174
|
+
name = arg.value.to_sym # steep:ignore
|
|
175
|
+
registry[name] = name
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# @rbs node: RuboCop::AST::Node
|
|
181
|
+
def on_ivar(node) #: void
|
|
182
|
+
return if same_named_method_body?(node)
|
|
183
|
+
return if in_self_changing_block?(node)
|
|
184
|
+
return if self_referential_write?(node)
|
|
185
|
+
|
|
186
|
+
current_scope.violations << Violation.new(node:, singleton: civar?(node))
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
private
|
|
190
|
+
|
|
191
|
+
attr_reader :scope_stack #: Array[Scope] -- the scopes of the classes/modules currently being visited
|
|
192
|
+
|
|
193
|
+
def push_scope #: void
|
|
194
|
+
scope_stack.push(
|
|
195
|
+
Scope.new(instance_readers: {}, class_readers: {}, violations: [], singleton_depth: 0)
|
|
196
|
+
)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def pop_scope #: void
|
|
200
|
+
scope_stack.pop
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def current_scope #: Scope
|
|
204
|
+
scope_stack.last or raise
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def singleton_context? #: bool
|
|
208
|
+
current_scope.singleton_depth.positive?
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Whether we are outside of any class/module, at the top level of the
|
|
212
|
+
# file. Readers are never registered there, so that a reader defined
|
|
213
|
+
# at the top level cannot unexpectedly match an unrelated top-level
|
|
214
|
+
# instance variable.
|
|
215
|
+
def top_level? #: bool
|
|
216
|
+
scope_stack.size == 1
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def report_violations #: void
|
|
220
|
+
current_scope.violations.each { report_violation(_1) }
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# @rbs violation: Violation
|
|
224
|
+
def report_violation(violation) #: void
|
|
225
|
+
node = violation.node
|
|
226
|
+
registry = violation.singleton ? current_scope.class_readers : current_scope.instance_readers
|
|
227
|
+
reader = registry[bare_ivar_name(node)]
|
|
228
|
+
|
|
229
|
+
add_offense(node, message: format(MSG, ivar: node.source)) do |corrector|
|
|
230
|
+
corrector.replace(node, reader.to_s) if reader
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Whether the instance variable sits in a singleton (class-level)
|
|
235
|
+
# context: inside `def self.x`, or inside `def x` that is itself
|
|
236
|
+
# nested in `class << self`.
|
|
237
|
+
#
|
|
238
|
+
# @rbs node: RuboCop::AST::Node
|
|
239
|
+
def civar?(node) #: bool
|
|
240
|
+
method_node = enclosing_def(node)
|
|
241
|
+
return true unless method_node
|
|
242
|
+
return true if method_node.defs_type?
|
|
243
|
+
|
|
244
|
+
boundary = method_node.each_ancestor(:sclass, :class, :module).first
|
|
245
|
+
boundary&.sclass_type? || false
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Whether `node` is an instance variable read inside the body of a
|
|
249
|
+
# method of the same name, regardless of what else that method's
|
|
250
|
+
# body does.
|
|
251
|
+
#
|
|
252
|
+
# @rbs node: RuboCop::AST::Node
|
|
253
|
+
def same_named_method_body?(node) #: bool
|
|
254
|
+
method_node = enclosing_def(node)
|
|
255
|
+
return false unless method_node
|
|
256
|
+
|
|
257
|
+
bare_ivar_name(node) == bare_method_name(method_node)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Whether `node` sits inside a block that rebinds `self` away from the
|
|
261
|
+
# enclosing class/module (`klass.instance_eval { @foo }`), making it
|
|
262
|
+
# impossible to tell whose ivar is actually being read. The search
|
|
263
|
+
# stops at the nearest enclosing `def`/`defs`, since a method
|
|
264
|
+
# definition always establishes its own `self` regardless of any
|
|
265
|
+
# block it happens to be lexically nested in.
|
|
266
|
+
#
|
|
267
|
+
# @rbs node: RuboCop::AST::Node
|
|
268
|
+
def in_self_changing_block?(node) #: bool
|
|
269
|
+
node.each_ancestor(:def, :defs, :block, :numblock).each do |ancestor|
|
|
270
|
+
case ancestor.type
|
|
271
|
+
when :def, :defs
|
|
272
|
+
return false
|
|
273
|
+
else
|
|
274
|
+
return true if self_changing_block?(ancestor)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
false
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# @rbs node: RuboCop::AST::Node
|
|
282
|
+
def self_changing_block?(node) #: bool
|
|
283
|
+
block = node #: RuboCop::AST::BlockNode
|
|
284
|
+
send_node = block.send_node
|
|
285
|
+
receiver = send_node.receiver
|
|
286
|
+
|
|
287
|
+
return false unless receiver
|
|
288
|
+
return false if receiver.self_type?
|
|
289
|
+
|
|
290
|
+
SELF_CHANGING_BLOCK_METHODS.include?(send_node.method_name)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Whether `node` is an instance variable read that appears anywhere
|
|
294
|
+
# in the value being assigned to that very same instance variable —
|
|
295
|
+
# whether by a plain `=` (`@x = @x + 1`, `@x = process(@x)`) or a
|
|
296
|
+
# compound assignment (`@x += @x + 1`, `@x += process(@x)`,
|
|
297
|
+
# `@x ||= ...`, `@x &&= ...`). Rewriting an instance variable is
|
|
298
|
+
# always allowed to read that same instance variable freely while
|
|
299
|
+
# computing its new value, wherever in the expression it's used. The
|
|
300
|
+
# search stops at the nearest enclosing `def`/`defs`, so a variable
|
|
301
|
+
# of the same name assigned in an unrelated enclosing method doesn't
|
|
302
|
+
# accidentally match.
|
|
303
|
+
#
|
|
304
|
+
# @rbs node: RuboCop::AST::Node
|
|
305
|
+
def self_referential_write?(node) #: bool
|
|
306
|
+
ancestor = node.each_ancestor(:ivasgn, :op_asgn, :or_asgn, :and_asgn, :def, :defs).first
|
|
307
|
+
return false unless ancestor
|
|
308
|
+
|
|
309
|
+
target = assignment_target(ancestor)
|
|
310
|
+
return false unless target
|
|
311
|
+
|
|
312
|
+
target.children.first == node.children.first
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# The instance variable being assigned by `node`, if `node` is a
|
|
316
|
+
# plain assignment to one (`ivasgn`) or a compound assignment whose
|
|
317
|
+
# target is one; `nil` otherwise, including when `node` is a
|
|
318
|
+
# `def`/`defs` boundary the search above stops at.
|
|
319
|
+
#
|
|
320
|
+
# @rbs node: RuboCop::AST::Node
|
|
321
|
+
def assignment_target(node) #: RuboCop::AST::Node?
|
|
322
|
+
return node if node.ivasgn_type?
|
|
323
|
+
return nil unless node.op_asgn_type? || node.or_asgn_type? || node.and_asgn_type?
|
|
324
|
+
|
|
325
|
+
lhs = node.children.first
|
|
326
|
+
lhs if lhs.ivasgn_type?
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# @rbs node: RuboCop::AST::Node
|
|
330
|
+
def enclosing_def(node) #: RuboCop::AST::DefNode?
|
|
331
|
+
node.each_ancestor(:any_def).first #: RuboCop::AST::DefNode?
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# @rbs node: RuboCop::AST::DefNode
|
|
335
|
+
def bare_method_name(node) #: Symbol
|
|
336
|
+
node.method_name.to_s.sub(/[=?]$/, "").to_sym
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# @rbs node: RuboCop::AST::Node
|
|
340
|
+
def bare_ivar_name(node) #: Symbol
|
|
341
|
+
node.children.first.to_s.delete_prefix("@").to_sym
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lint_roller"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
module RuboCop
|
|
7
|
+
module InstanceVariableAccess
|
|
8
|
+
# A plugin that integrates rubocop-instance_variable_access with RuboCop's plugin system.
|
|
9
|
+
class Plugin < LintRoller::Plugin
|
|
10
|
+
def about #: LintRoller::About
|
|
11
|
+
LintRoller::About.new(
|
|
12
|
+
name: "rubocop-instance_variable_access",
|
|
13
|
+
version: VERSION,
|
|
14
|
+
homepage: "https://github.com/tk0miya/rubocop-instance_variable_access",
|
|
15
|
+
description: "A RuboCop extension that enforces accessing instance variables through reader methods."
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @rbs context: untyped
|
|
20
|
+
def supported?(context) #: bool
|
|
21
|
+
context.engine == :rubocop
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @rbs _context: untyped
|
|
25
|
+
def rules(_context) #: LintRoller::Rules
|
|
26
|
+
project_root = Pathname.new(__dir__.to_s).join("../../..")
|
|
27
|
+
|
|
28
|
+
LintRoller::Rules.new(
|
|
29
|
+
type: :path,
|
|
30
|
+
config_format: :rubocop,
|
|
31
|
+
value: project_root.join("config", "default.yml")
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Generated from lib/rubocop/cop/style/instance_variable_access.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Style
|
|
6
|
+
# Checks that instance variables (including class instance variables) are
|
|
7
|
+
# accessed through reader methods rather than referenced directly, even
|
|
8
|
+
# from within the class that defines them.
|
|
9
|
+
#
|
|
10
|
+
# Writing to an instance variable (via `=`, `+=`, `||=`, `&&=`, or
|
|
11
|
+
# multiple assignment) is always allowed, so a memoization idiom such
|
|
12
|
+
# as `@memo ||= expensive_call` is unaffected, since it writes to the
|
|
13
|
+
# variable rather than reading it. Reading that same instance variable
|
|
14
|
+
# anywhere else while computing the value being assigned to it is
|
|
15
|
+
# allowed too, whether the assignment is a plain `=`
|
|
16
|
+
# (`@count = @count + 1`, `@count = compute(@count)`) or a compound
|
|
17
|
+
# assignment that reads it again explicitly
|
|
18
|
+
# (`@count += compute(@count)`).
|
|
19
|
+
#
|
|
20
|
+
# A method whose name matches the instance variable (`def name; ...;
|
|
21
|
+
# @name; end`, or its class-method equivalent `def self.name; ...;
|
|
22
|
+
# @name; end`) is treated as that variable's own reader, so referencing
|
|
23
|
+
# it there is not itself an offense, no matter what else the method's
|
|
24
|
+
# body does.
|
|
25
|
+
#
|
|
26
|
+
# An instance variable read inside a block passed to `instance_eval`,
|
|
27
|
+
# `instance_exec`, `class_eval`, or `module_eval` on anything other
|
|
28
|
+
# than `self` is not flagged, since `self` (and therefore whose
|
|
29
|
+
# instance variable is actually being read) changes inside that
|
|
30
|
+
# block.
|
|
31
|
+
#
|
|
32
|
+
# @safety
|
|
33
|
+
# Autocorrection only rewrites `@foo` to `foo` when an `attr_reader`/
|
|
34
|
+
# `attr_accessor` for the same instance/singleton context already
|
|
35
|
+
# exists in the same class body. A hand-written reader like the one
|
|
36
|
+
# above doesn't count, because there is no way to tell whether it is
|
|
37
|
+
# safe to call in place of `@foo` (it could have side effects, or
|
|
38
|
+
# require arguments). Otherwise, only the offense is reported; this
|
|
39
|
+
# cop never generates an `attr_reader`.
|
|
40
|
+
#
|
|
41
|
+
# @example
|
|
42
|
+
# # bad
|
|
43
|
+
# class Person
|
|
44
|
+
# def full_name
|
|
45
|
+
# "#{@first_name} #{@last_name}"
|
|
46
|
+
# end
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
# # good
|
|
50
|
+
# class Person
|
|
51
|
+
# attr_reader :first_name, :last_name
|
|
52
|
+
#
|
|
53
|
+
# def full_name
|
|
54
|
+
# "#{first_name} #{last_name}"
|
|
55
|
+
# end
|
|
56
|
+
# end
|
|
57
|
+
#
|
|
58
|
+
# # good (the reader definition itself is not an offense)
|
|
59
|
+
# class Person
|
|
60
|
+
# private
|
|
61
|
+
#
|
|
62
|
+
# def first_name
|
|
63
|
+
# @first_name
|
|
64
|
+
# end
|
|
65
|
+
# end
|
|
66
|
+
#
|
|
67
|
+
# # good (still just the reader, whatever else it does first)
|
|
68
|
+
# class Person
|
|
69
|
+
# def first_name
|
|
70
|
+
# logger.debug("first_name accessed")
|
|
71
|
+
# @first_name
|
|
72
|
+
# end
|
|
73
|
+
# end
|
|
74
|
+
#
|
|
75
|
+
# # good (memoization writes to the instance variable rather than reading it)
|
|
76
|
+
# class Person
|
|
77
|
+
# def first_name
|
|
78
|
+
# @first_name ||= compute_first_name
|
|
79
|
+
# end
|
|
80
|
+
# end
|
|
81
|
+
#
|
|
82
|
+
# # good (reading `@count` anywhere while rewriting it is fine, `=` or `+=`)
|
|
83
|
+
# class Counter
|
|
84
|
+
# def increment
|
|
85
|
+
# @count = compute(@count)
|
|
86
|
+
# @count += compute(@count)
|
|
87
|
+
# end
|
|
88
|
+
# end
|
|
89
|
+
#
|
|
90
|
+
# # good (`self` changes inside the block, so this isn't Person's own ivar)
|
|
91
|
+
# class Person
|
|
92
|
+
# def borrow_name_from(other)
|
|
93
|
+
# other.instance_eval { @first_name }
|
|
94
|
+
# end
|
|
95
|
+
# end
|
|
96
|
+
class InstanceVariableAccess < Base
|
|
97
|
+
extend AutoCorrector
|
|
98
|
+
|
|
99
|
+
MSG: ::String
|
|
100
|
+
|
|
101
|
+
RESTRICT_ON_SEND: untyped
|
|
102
|
+
|
|
103
|
+
# Methods that run their block with `self` rebound to something other
|
|
104
|
+
# than the block's lexical `self`, so an instance variable read inside
|
|
105
|
+
# such a block cannot be attributed to the enclosing class/module.
|
|
106
|
+
SELF_CHANGING_BLOCK_METHODS: untyped
|
|
107
|
+
|
|
108
|
+
def attr_reader_or_accessor?: (RuboCop::AST::Node node) -> bool
|
|
109
|
+
|
|
110
|
+
# A candidate offense: an instance variable read that isn't inside the
|
|
111
|
+
# body of a method of the same name. Resolved against the enclosing
|
|
112
|
+
# scope's readers once the whole class/module body has been seen.
|
|
113
|
+
class Violation < Data
|
|
114
|
+
attr_reader node(): RuboCop::AST::Node
|
|
115
|
+
|
|
116
|
+
attr_reader singleton(): bool
|
|
117
|
+
|
|
118
|
+
def self.new: (RuboCop::AST::Node node, bool singleton) -> instance
|
|
119
|
+
| (node: RuboCop::AST::Node, singleton: bool) -> instance
|
|
120
|
+
|
|
121
|
+
def self.members: () -> [ :node, :singleton ]
|
|
122
|
+
|
|
123
|
+
def members: () -> [ :node, :singleton ]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# The readers and pending violations for a single class/module body
|
|
127
|
+
# (or the top level, for the root scope).
|
|
128
|
+
class Scope < Struct[Hash[Symbol, Symbol] | Array[Violation] | Integer]
|
|
129
|
+
attr_accessor instance_readers(): Hash[Symbol, Symbol]
|
|
130
|
+
|
|
131
|
+
attr_accessor class_readers(): Hash[Symbol, Symbol]
|
|
132
|
+
|
|
133
|
+
attr_accessor violations(): Array[Violation]
|
|
134
|
+
|
|
135
|
+
attr_accessor singleton_depth(): Integer
|
|
136
|
+
|
|
137
|
+
def self.new: (?instance_readers: Hash[Symbol, Symbol], ?class_readers: Hash[Symbol, Symbol], ?violations: Array[Violation], ?singleton_depth: Integer) -> instance
|
|
138
|
+
| ({ ?instance_readers: Hash[Symbol, Symbol], ?class_readers: Hash[Symbol, Symbol], ?violations: Array[Violation], ?singleton_depth: Integer }) -> instance
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def on_new_investigation: () -> void
|
|
142
|
+
|
|
143
|
+
def on_investigation_end: () -> void
|
|
144
|
+
|
|
145
|
+
def on_class: (untyped _node) -> void
|
|
146
|
+
|
|
147
|
+
alias on_module on_class
|
|
148
|
+
|
|
149
|
+
# @rbs _node: RuboCop::AST::Node
|
|
150
|
+
def after_class: (untyped _node) -> void
|
|
151
|
+
|
|
152
|
+
alias after_module after_class
|
|
153
|
+
|
|
154
|
+
# @rbs _node: RuboCop::AST::Node
|
|
155
|
+
def on_sclass: (untyped _node) -> void
|
|
156
|
+
|
|
157
|
+
# @rbs _node: RuboCop::AST::Node
|
|
158
|
+
def after_sclass: (untyped _node) -> void
|
|
159
|
+
|
|
160
|
+
# @rbs node: RuboCop::AST::SendNode
|
|
161
|
+
def on_send: (RuboCop::AST::SendNode node) -> void
|
|
162
|
+
|
|
163
|
+
# @rbs node: RuboCop::AST::Node
|
|
164
|
+
def on_ivar: (RuboCop::AST::Node node) -> void
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
attr_reader scope_stack: Array[Scope]
|
|
169
|
+
|
|
170
|
+
def push_scope: () -> void
|
|
171
|
+
|
|
172
|
+
def pop_scope: () -> void
|
|
173
|
+
|
|
174
|
+
def current_scope: () -> Scope
|
|
175
|
+
|
|
176
|
+
def singleton_context?: () -> bool
|
|
177
|
+
|
|
178
|
+
# Whether we are outside of any class/module, at the top level of the
|
|
179
|
+
# file. Readers are never registered there, so that a reader defined
|
|
180
|
+
# at the top level cannot unexpectedly match an unrelated top-level
|
|
181
|
+
# instance variable.
|
|
182
|
+
def top_level?: () -> bool
|
|
183
|
+
|
|
184
|
+
def report_violations: () -> void
|
|
185
|
+
|
|
186
|
+
# @rbs violation: Violation
|
|
187
|
+
def report_violation: (Violation violation) -> void
|
|
188
|
+
|
|
189
|
+
# Whether the instance variable sits in a singleton (class-level)
|
|
190
|
+
# context: inside `def self.x`, or inside `def x` that is itself
|
|
191
|
+
# nested in `class << self`.
|
|
192
|
+
#
|
|
193
|
+
# @rbs node: RuboCop::AST::Node
|
|
194
|
+
def civar?: (RuboCop::AST::Node node) -> bool
|
|
195
|
+
|
|
196
|
+
# Whether `node` is an instance variable read inside the body of a
|
|
197
|
+
# method of the same name, regardless of what else that method's
|
|
198
|
+
# body does.
|
|
199
|
+
#
|
|
200
|
+
# @rbs node: RuboCop::AST::Node
|
|
201
|
+
def same_named_method_body?: (RuboCop::AST::Node node) -> bool
|
|
202
|
+
|
|
203
|
+
# Whether `node` sits inside a block that rebinds `self` away from the
|
|
204
|
+
# enclosing class/module (`klass.instance_eval { @foo }`), making it
|
|
205
|
+
# impossible to tell whose ivar is actually being read. The search
|
|
206
|
+
# stops at the nearest enclosing `def`/`defs`, since a method
|
|
207
|
+
# definition always establishes its own `self` regardless of any
|
|
208
|
+
# block it happens to be lexically nested in.
|
|
209
|
+
#
|
|
210
|
+
# @rbs node: RuboCop::AST::Node
|
|
211
|
+
def in_self_changing_block?: (RuboCop::AST::Node node) -> bool
|
|
212
|
+
|
|
213
|
+
# @rbs node: RuboCop::AST::Node
|
|
214
|
+
def self_changing_block?: (RuboCop::AST::Node node) -> bool
|
|
215
|
+
|
|
216
|
+
# Whether `node` is an instance variable read that appears anywhere
|
|
217
|
+
# in the value being assigned to that very same instance variable —
|
|
218
|
+
# whether by a plain `=` (`@x = @x + 1`, `@x = process(@x)`) or a
|
|
219
|
+
# compound assignment (`@x += @x + 1`, `@x += process(@x)`,
|
|
220
|
+
# `@x ||= ...`, `@x &&= ...`). Rewriting an instance variable is
|
|
221
|
+
# always allowed to read that same instance variable freely while
|
|
222
|
+
# computing its new value, wherever in the expression it's used. The
|
|
223
|
+
# search stops at the nearest enclosing `def`/`defs`, so a variable
|
|
224
|
+
# of the same name assigned in an unrelated enclosing method doesn't
|
|
225
|
+
# accidentally match.
|
|
226
|
+
#
|
|
227
|
+
# @rbs node: RuboCop::AST::Node
|
|
228
|
+
def self_referential_write?: (RuboCop::AST::Node node) -> bool
|
|
229
|
+
|
|
230
|
+
# The instance variable being assigned by `node`, if `node` is a
|
|
231
|
+
# plain assignment to one (`ivasgn`) or a compound assignment whose
|
|
232
|
+
# target is one; `nil` otherwise, including when `node` is a
|
|
233
|
+
# `def`/`defs` boundary the search above stops at.
|
|
234
|
+
#
|
|
235
|
+
# @rbs node: RuboCop::AST::Node
|
|
236
|
+
def assignment_target: (RuboCop::AST::Node node) -> RuboCop::AST::Node?
|
|
237
|
+
|
|
238
|
+
# @rbs node: RuboCop::AST::Node
|
|
239
|
+
def enclosing_def: (RuboCop::AST::Node node) -> RuboCop::AST::DefNode?
|
|
240
|
+
|
|
241
|
+
# @rbs node: RuboCop::AST::DefNode
|
|
242
|
+
def bare_method_name: (RuboCop::AST::DefNode node) -> Symbol
|
|
243
|
+
|
|
244
|
+
# @rbs node: RuboCop::AST::Node
|
|
245
|
+
def bare_ivar_name: (RuboCop::AST::Node node) -> Symbol
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Generated from lib/rubocop/instance_variable_access/plugin.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module InstanceVariableAccess
|
|
5
|
+
# A plugin that integrates rubocop-instance_variable_access with RuboCop's plugin system.
|
|
6
|
+
class Plugin < LintRoller::Plugin
|
|
7
|
+
def about: () -> LintRoller::About
|
|
8
|
+
|
|
9
|
+
# @rbs context: untyped
|
|
10
|
+
def supported?: (untyped context) -> bool
|
|
11
|
+
|
|
12
|
+
# @rbs _context: untyped
|
|
13
|
+
def rules: (untyped _context) -> LintRoller::Rules
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rubocop-instance_variable_access
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Takeshi KOMIYA
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: lint_roller
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rubocop
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.72'
|
|
33
|
+
- - "<"
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: '2.0'
|
|
36
|
+
type: :runtime
|
|
37
|
+
prerelease: false
|
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '1.72'
|
|
43
|
+
- - "<"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '2.0'
|
|
46
|
+
description: rubocop-instance_variable_access provides the Style/InstanceVariableAccess
|
|
47
|
+
cop, which flags direct reads of instance variables (including class instance variables)
|
|
48
|
+
outside of their reader method, encouraging access via attr_reader/attr_accessor
|
|
49
|
+
or a hand-written reader even from within the defining class.
|
|
50
|
+
email:
|
|
51
|
+
- i.tkomiya@gmail.com
|
|
52
|
+
executables: []
|
|
53
|
+
extensions: []
|
|
54
|
+
extra_rdoc_files: []
|
|
55
|
+
files:
|
|
56
|
+
- CHANGELOG.md
|
|
57
|
+
- CODE_OF_CONDUCT.md
|
|
58
|
+
- LICENSE.txt
|
|
59
|
+
- README.md
|
|
60
|
+
- config/default.yml
|
|
61
|
+
- lib/rubocop-instance_variable_access.rb
|
|
62
|
+
- lib/rubocop/cop/instance_variable_access_cops.rb
|
|
63
|
+
- lib/rubocop/cop/style/instance_variable_access.rb
|
|
64
|
+
- lib/rubocop/instance_variable_access.rb
|
|
65
|
+
- lib/rubocop/instance_variable_access/plugin.rb
|
|
66
|
+
- lib/rubocop/instance_variable_access/version.rb
|
|
67
|
+
- sig/rubocop-instance_variable_access.rbs
|
|
68
|
+
- sig/rubocop/cop/instance_variable_access_cops.rbs
|
|
69
|
+
- sig/rubocop/cop/style/instance_variable_access.rbs
|
|
70
|
+
- sig/rubocop/instance_variable_access.rbs
|
|
71
|
+
- sig/rubocop/instance_variable_access/plugin.rbs
|
|
72
|
+
- sig/rubocop/instance_variable_access/version.rbs
|
|
73
|
+
homepage: https://github.com/tk0miya/rubocop-instance_variable_access
|
|
74
|
+
licenses:
|
|
75
|
+
- MIT
|
|
76
|
+
metadata:
|
|
77
|
+
allowed_push_host: https://rubygems.org
|
|
78
|
+
homepage_uri: https://github.com/tk0miya/rubocop-instance_variable_access
|
|
79
|
+
source_code_uri: https://github.com/tk0miya/rubocop-instance_variable_access.git
|
|
80
|
+
changelog_uri: https://github.com/tk0miya/rubocop-instance_variable_access/blob/main/CHANGELOG.md
|
|
81
|
+
rubygems_mfa_required: 'true'
|
|
82
|
+
default_lint_roller_plugin: RuboCop::InstanceVariableAccess::Plugin
|
|
83
|
+
rdoc_options: []
|
|
84
|
+
require_paths:
|
|
85
|
+
- lib
|
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: 3.3.0
|
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
requirements: []
|
|
97
|
+
rubygems_version: 4.0.16
|
|
98
|
+
specification_version: 4
|
|
99
|
+
summary: A RuboCop extension that enforces accessing instance variables through reader
|
|
100
|
+
methods.
|
|
101
|
+
test_files: []
|