strict_ivars 0.5.0 → 0.6.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 +4 -4
- data/README.md +0 -20
- data/lib/strict_ivars/base_processor.rb +57 -0
- data/lib/strict_ivars/configuration.rb +2 -1
- data/lib/strict_ivars/processor.rb +5 -45
- data/lib/strict_ivars/version.rb +1 -1
- data/lib/strict_ivars.rb +53 -10
- metadata +2 -2
- data/lib/strict_ivars/patch_eval.rb +0 -84
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6e1244d297abc3e3ade0882ca3a8767b63701c42721a7199803ad75b46c566ed
|
4
|
+
data.tar.gz: c141c19397e2942a40bb049ca77790ca9b29a6de9e0cd30e218162b1e51c4ee7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8cd2d471ca00c86a910843da2cacf803d71f30d48c946f469a5f18ef34c23b54d99d20897501a769695ff37114b677b8ce25c927a135876d99f1202324ad2e8f
|
7
|
+
data.tar.gz: ab6d3154078514bb0bd9615bae2cecdda743cb7a3dcd5f55d8571fae1f5201f94d090ebffd582881876f3b66bdcfd2035e4d600b77b7fa3350759f33deb4850a
|
data/README.md
CHANGED
@@ -26,26 +26,6 @@ The replacement happens on load, so you never see this in your source code. It
|
|
26
26
|
|
27
27
|
The real guard is a little uglier than this. It uses `::Kernel.raise` so it’s compatible with `BasicObject`. It also raises a `StrictIvars::NameError` with a helpful message mentioning the name of the instance variable, and that inherits from `NameError`, allowing you to rescue either `NameError` or `StrictIvars::NameError`.
|
28
28
|
|
29
|
-
**When you check defined already**
|
30
|
-
|
31
|
-
Within the same context (e.g. class definition, module definition, block, method), if you check `defined?`, Strict Ivars will not add a guard to reads of the checked instance variable.
|
32
|
-
|
33
|
-
```ruby
|
34
|
-
if defined?(@foo)
|
35
|
-
@foo
|
36
|
-
end
|
37
|
-
```
|
38
|
-
|
39
|
-
This applies even if the read is not in one of the conditional’s branches since you’ve indicated local awareness of the potential for this instance variable not being defined.
|
40
|
-
|
41
|
-
```ruby
|
42
|
-
if defined?(@foo)
|
43
|
-
# anything
|
44
|
-
end
|
45
|
-
|
46
|
-
@foo
|
47
|
-
```
|
48
|
-
|
49
29
|
**Writes:**
|
50
30
|
|
51
31
|
Strict Ivars doesn’t apply to writes, since these are considered the authoritative source of the instance variable definitions.
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
class StrictIvars::BaseProcessor < Prism::Visitor
|
6
|
+
EVAL_METHODS = Set[:class_eval, :module_eval, :instance_eval, :eval].freeze
|
7
|
+
|
8
|
+
#: (String) -> String
|
9
|
+
def self.call(source)
|
10
|
+
visitor = new
|
11
|
+
visitor.visit(Prism.parse(source).value)
|
12
|
+
buffer = source.dup
|
13
|
+
annotations = visitor.annotations
|
14
|
+
annotations.sort_by!(&:first)
|
15
|
+
|
16
|
+
annotations.reverse_each do |offset, string|
|
17
|
+
buffer.insert(offset, string)
|
18
|
+
end
|
19
|
+
|
20
|
+
buffer
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@context = Set[]
|
25
|
+
@annotations = []
|
26
|
+
end
|
27
|
+
|
28
|
+
#: Array[[Integer, :start_ivar_read | :end_ivar_read, Symbol]]
|
29
|
+
attr_reader :annotations
|
30
|
+
|
31
|
+
def visit_call_node(node)
|
32
|
+
name = node.name
|
33
|
+
|
34
|
+
if EVAL_METHODS.include?(name) && (arguments = node.arguments)
|
35
|
+
location = arguments.location
|
36
|
+
|
37
|
+
if node.receiver
|
38
|
+
receiver_local = "__eval_receiver_#{SecureRandom.hex(8)}__"
|
39
|
+
receiver_location = node.receiver.location
|
40
|
+
|
41
|
+
@annotations.push(
|
42
|
+
[receiver_location.start_character_offset, "(#{receiver_local} = "],
|
43
|
+
[receiver_location.end_character_offset, ")"],
|
44
|
+
[location.start_character_offset, "*(::StrictIvars.process_eval_args(#{receiver_local}, :#{name}, "],
|
45
|
+
[location.end_character_offset, "))"]
|
46
|
+
)
|
47
|
+
else
|
48
|
+
@annotations.push(
|
49
|
+
[location.start_character_offset, "*(::StrictIvars.process_eval_args(self, :#{name}, "],
|
50
|
+
[location.end_character_offset, "))"]
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
super
|
56
|
+
end
|
57
|
+
end
|
@@ -22,7 +22,8 @@ class StrictIvars::Configuration
|
|
22
22
|
end
|
23
23
|
|
24
24
|
#: (String) -> bool
|
25
|
-
def match(path)
|
25
|
+
def match?(path)
|
26
|
+
return false unless String === path
|
26
27
|
path = File.absolute_path(path)
|
27
28
|
return false if @exclude.any? { |pattern| File.fnmatch?(pattern, path) }
|
28
29
|
return true if @include.any? { |pattern| File.fnmatch?(pattern, path) }
|
@@ -1,36 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class StrictIvars::Processor <
|
4
|
-
#: (String) -> String
|
5
|
-
def self.call(source)
|
6
|
-
visitor = new
|
7
|
-
visitor.visit(Prism.parse(source).value)
|
8
|
-
buffer = source.dup
|
9
|
-
annotations = visitor.annotations
|
10
|
-
annotations.sort_by!(&:first)
|
11
|
-
|
12
|
-
annotations.reverse_each do |offset, action, name|
|
13
|
-
case action
|
14
|
-
when :start
|
15
|
-
buffer.insert(offset, "(defined?(#{name}) ? ")
|
16
|
-
when :end
|
17
|
-
buffer.insert(offset, " : (::Kernel.raise(::StrictIvars::NameError.new('Undefined instance variable #{name}'))))")
|
18
|
-
else
|
19
|
-
raise "Invalid annotation"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
buffer
|
24
|
-
end
|
25
|
-
|
26
|
-
def initialize
|
27
|
-
@context = Set[]
|
28
|
-
@annotations = []
|
29
|
-
end
|
30
|
-
|
31
|
-
#: Array[[Integer, :start | :end, Symbol]]
|
32
|
-
attr_reader :annotations
|
33
|
-
|
3
|
+
class StrictIvars::Processor < StrictIvars::BaseProcessor
|
34
4
|
#: (Prism::ClassNode) -> void
|
35
5
|
def visit_class_node(node)
|
36
6
|
new_context { super }
|
@@ -51,17 +21,6 @@ class StrictIvars::Processor < Prism::Visitor
|
|
51
21
|
new_context { super }
|
52
22
|
end
|
53
23
|
|
54
|
-
#: (Prism::DefinedNode) -> void
|
55
|
-
def visit_defined_node(node)
|
56
|
-
value = node.value
|
57
|
-
|
58
|
-
if Prism::InstanceVariableReadNode === value
|
59
|
-
@context << value.name
|
60
|
-
end
|
61
|
-
|
62
|
-
super
|
63
|
-
end
|
64
|
-
|
65
24
|
#: (Prism::DefNode) -> void
|
66
25
|
def visit_def_node(node)
|
67
26
|
new_context { super }
|
@@ -95,9 +54,10 @@ class StrictIvars::Processor < Prism::Visitor
|
|
95
54
|
|
96
55
|
@context << name
|
97
56
|
|
98
|
-
@annotations
|
99
|
-
[location.start_character_offset,
|
100
|
-
[location.end_character_offset, :
|
57
|
+
@annotations.push(
|
58
|
+
[location.start_character_offset, "(defined?(#{name}) ? "],
|
59
|
+
[location.end_character_offset, " : (::Kernel.raise(::StrictIvars::NameError.new('Undefined instance variable #{name}'))))"]
|
60
|
+
)
|
101
61
|
end
|
102
62
|
|
103
63
|
super
|
data/lib/strict_ivars/version.rb
CHANGED
data/lib/strict_ivars.rb
CHANGED
@@ -3,28 +3,71 @@
|
|
3
3
|
require "prism"
|
4
4
|
require "require-hooks/setup"
|
5
5
|
require "strict_ivars/version"
|
6
|
+
require "strict_ivars/base_processor"
|
7
|
+
require "strict_ivars/processor"
|
6
8
|
require "strict_ivars/configuration"
|
7
9
|
|
8
10
|
module StrictIvars
|
9
11
|
NameError = Class.new(::NameError)
|
10
12
|
|
13
|
+
EMPTY_ARRAY = [].freeze
|
14
|
+
EVERYTHING = ["**/*"].freeze
|
15
|
+
METHOD_METHOD = Module.instance_method(:method)
|
16
|
+
|
11
17
|
CONFIG = Configuration.new
|
12
18
|
|
13
19
|
#: (include: Array[String], exclude: Array[String]) -> void
|
14
|
-
def self.init(include:
|
15
|
-
|
16
|
-
|
17
|
-
CONFIG.include(*include)
|
18
|
-
CONFIG.exclude(*exclude)
|
20
|
+
def self.init(include: EMPTY_ARRAY, exclude: EMPTY_ARRAY)
|
21
|
+
CONFIG.include(*include) unless include.length == 0
|
22
|
+
CONFIG.exclude(*exclude) unless exclude.length == 0
|
19
23
|
|
20
24
|
RequireHooks.source_transform(
|
21
|
-
patterns:
|
22
|
-
exclude_patterns:
|
25
|
+
patterns: EVERYTHING,
|
26
|
+
exclude_patterns: EMPTY_ARRAY
|
23
27
|
) do |path, source|
|
24
28
|
source ||= File.read(path)
|
25
|
-
|
29
|
+
|
30
|
+
if CONFIG.match?(path)
|
31
|
+
Processor.call(source)
|
32
|
+
else
|
33
|
+
BaseProcessor.call(source)
|
34
|
+
end
|
26
35
|
end
|
27
36
|
end
|
28
|
-
end
|
29
37
|
|
30
|
-
|
38
|
+
def self.process_eval_args(receiver, method_name, *args)
|
39
|
+
method = METHOD_METHOD.bind_call(receiver, method_name)
|
40
|
+
owner = method.owner
|
41
|
+
|
42
|
+
source, file = nil
|
43
|
+
|
44
|
+
case method_name
|
45
|
+
when :class_eval, :module_eval
|
46
|
+
if Module == owner
|
47
|
+
source, file = args
|
48
|
+
end
|
49
|
+
when :instance_eval
|
50
|
+
if BasicObject == owner
|
51
|
+
source, file = args
|
52
|
+
end
|
53
|
+
when :eval
|
54
|
+
if Kernel == owner
|
55
|
+
source, binding, file = args
|
56
|
+
elsif Binding == owner
|
57
|
+
source, file = args
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
if String === source
|
62
|
+
file ||= caller_locations(1, 1).first&.path
|
63
|
+
|
64
|
+
if CONFIG.match?(file)
|
65
|
+
args[0] = Processor.call(source)
|
66
|
+
else
|
67
|
+
args[0] = BaseProcessor.call(source)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
args
|
72
|
+
end
|
73
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strict_ivars
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joel Drapper
|
@@ -47,8 +47,8 @@ files:
|
|
47
47
|
- LICENSE.txt
|
48
48
|
- README.md
|
49
49
|
- lib/strict_ivars.rb
|
50
|
+
- lib/strict_ivars/base_processor.rb
|
50
51
|
- lib/strict_ivars/configuration.rb
|
51
|
-
- lib/strict_ivars/patch_eval.rb
|
52
52
|
- lib/strict_ivars/processor.rb
|
53
53
|
- lib/strict_ivars/version.rb
|
54
54
|
homepage: https://github.com/joeldrapper/strict_ivars
|
@@ -1,84 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module StrictIvars
|
4
|
-
module ModuleEvalPatch
|
5
|
-
#: (String, ?String, ?Integer) -> void
|
6
|
-
#: () { () -> void } -> void
|
7
|
-
def class_eval(*args)
|
8
|
-
source, file, lineno = args
|
9
|
-
|
10
|
-
file ||= caller_locations(1, 1).first.path
|
11
|
-
|
12
|
-
if source && file && CONFIG.match(file)
|
13
|
-
args[0] = Processor.call(source)
|
14
|
-
end
|
15
|
-
|
16
|
-
super
|
17
|
-
end
|
18
|
-
|
19
|
-
#: (String, ?String, ?Integer) -> void
|
20
|
-
#: () { () -> void } -> void
|
21
|
-
def module_eval(*args)
|
22
|
-
source, file, lineno = args
|
23
|
-
|
24
|
-
file ||= caller_locations(1, 1).first.path
|
25
|
-
|
26
|
-
if source && file && CONFIG.match(file)
|
27
|
-
args[0] = Processor.call(source)
|
28
|
-
end
|
29
|
-
|
30
|
-
super
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
module InstanceEvalPatch
|
35
|
-
#: (String, ?String, ?Integer) -> void
|
36
|
-
#: () { () -> void } -> void
|
37
|
-
def instance_eval(*args)
|
38
|
-
source, file, lineno = args
|
39
|
-
|
40
|
-
file ||= caller_locations(1, 1).first.path
|
41
|
-
|
42
|
-
if source && file && CONFIG.match(file)
|
43
|
-
args[0] = Processor.call(source)
|
44
|
-
end
|
45
|
-
|
46
|
-
super
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
module KernelEvalPatch
|
51
|
-
#: (String, Binding, ?String, ?Integer) -> void
|
52
|
-
def eval(*args)
|
53
|
-
source, binding, file, lineno = args
|
54
|
-
|
55
|
-
file ||= caller_locations(1, 1).first.path
|
56
|
-
|
57
|
-
if source && file && CONFIG.match(file)
|
58
|
-
args[0] = Processor.call(source.to_s)
|
59
|
-
end
|
60
|
-
|
61
|
-
super
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
module BindingEvalPatch
|
66
|
-
#: (String, ?String, ?Integer) -> void
|
67
|
-
def eval(*args)
|
68
|
-
source, file, lineno = args
|
69
|
-
|
70
|
-
file ||= caller_locations(1, 1).first.path
|
71
|
-
|
72
|
-
if source && file && CONFIG.match(file)
|
73
|
-
args[0] = Processor.call(source.to_s)
|
74
|
-
end
|
75
|
-
|
76
|
-
super
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
Kernel.prepend(KernelEvalPatch)
|
81
|
-
Module.prepend(ModuleEvalPatch)
|
82
|
-
Binding.prepend(BindingEvalPatch)
|
83
|
-
BasicObject.prepend(InstanceEvalPatch)
|
84
|
-
end
|