strict_ivars 0.4.1 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2fdc65115a894c678d04614ece5855c2089003d320fe6bf22cb9c9ef0aa5de50
4
- data.tar.gz: cb51570cf483f8a952daf0eb4798ac430c699f5357474c80d37cf4c75cc8b073
3
+ metadata.gz: 65516e69e17c893c02c57e0cc0be683c57c9dc3fd91b80f56942453d5df43649
4
+ data.tar.gz: 16d3c581474be1f5a7500eb24e442ac8bcd54f55ee5d7afef6cdea3ef986381f
5
5
  SHA512:
6
- metadata.gz: 669e9cd076e7be979fa8d145cf9c0413794eeddd650dc0c183a67f2341809217dcd41059050fd63143311b9afc8c0c44dae82ade79e94967929c080d55da9bc0
7
- data.tar.gz: 901f1b60e232b8dccc0fbd6bf36f50b906b702e7396fffec47fa8e180e3390629ef0e7733e736e341f2ea482fc255d0870a96cbbccaf74cbfe0988a2d6df0cc0
6
+ metadata.gz: b92fa82ca1acbcef116a5ab531bb1a4ec08301db0b17eb9d5f5a99af47c29face2c82a51d0f63b905eec0a7506e2453b040731848a69afff951f6f44b18e789e
7
+ data.tar.gz: b650231f2312548af22bd11ecc031dbdcbf178a5ce4f501b29f48687a59209a6533c12a4ef8fe78a351cc03e591bbd3e01c1edf67f03568620279b3990ab0e63
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Strict Ivars
2
2
 
3
- Strict Ivars is a tiny pre-processor for Ruby that guards your instance variable reads, ensuring the instance variable is actually defined. This helps catch typos nice and early.
3
+ Strict Ivars is a tiny pre-processor for Ruby that guards your instance variable reads, ensuring the instance variable is actually defined. This helps catch typos nice and early. It‘s especially good when used with [Literal](https://literal.fun).
4
4
 
5
5
  ## How does it work?
6
6
 
@@ -26,6 +26,50 @@ 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
+ **Writes:**
50
+
51
+ Strict Ivars doesn’t apply to writes, since these are considered the authoritative source of the instance variable definitions.
52
+
53
+ ```ruby
54
+ @foo = 1
55
+ ```
56
+
57
+ **Or-writes:**
58
+
59
+ This is considered a definition and not guarded.
60
+
61
+ ```ruby
62
+ @foo ||= 1
63
+ ```
64
+
65
+ **And-writes:**
66
+
67
+ This is considered a definition and not guarded.
68
+
69
+ ```ruby
70
+ @foo &&= 1
71
+ ```
72
+
29
73
  ## Setup
30
74
 
31
75
  Install the gem by adding it to your `Gemfile` and running `bundle install`.
@@ -41,7 +85,7 @@ Now the gem is installed, you should require and initialize the gem as early as
41
85
  ```ruby
42
86
  require "strict_ivars"
43
87
 
44
- StrictIvars.init(include: ["#{Dir.pwd}/**/*"])
88
+ StrictIvars.init(include: ["#{Dir.pwd}/**/*"], exclude: ["#{Dir.pwd}/vendor/**/*"])
45
89
  ```
46
90
 
47
91
  You can pass an array of globs to `include:` and `exclude:`.
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class StrictIvars::Configuration
4
+ def initialize
5
+ @mutex = Mutex.new
6
+ @include = []
7
+ @exclude = []
8
+ end
9
+
10
+ #: (*String) -> void
11
+ def include(*patterns)
12
+ @mutex.synchronize do
13
+ @include.concat(patterns)
14
+ end
15
+ end
16
+
17
+ #: (*String) -> void
18
+ def exclude(*patterns)
19
+ @mutex.synchronize do
20
+ @exclude.concat(patterns)
21
+ end
22
+ end
23
+
24
+ #: (String) -> bool
25
+ def match(path)
26
+ path = File.absolute_path(path)
27
+ return false if @exclude.any? { |pattern| File.fnmatch?(pattern, path) }
28
+ return true if @include.any? { |pattern| File.fnmatch?(pattern, path) }
29
+ false
30
+ end
31
+ end
@@ -0,0 +1,84 @@
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StrictIvars
4
- VERSION = "0.4.1"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/strict_ivars.rb CHANGED
@@ -2,12 +2,21 @@
2
2
 
3
3
  require "prism"
4
4
  require "require-hooks/setup"
5
+ require "strict_ivars/version"
6
+ require "strict_ivars/configuration"
5
7
 
6
8
  module StrictIvars
7
9
  NameError = Class.new(::NameError)
8
10
 
11
+ CONFIG = Configuration.new
12
+
9
13
  #: (include: Array[String], exclude: Array[String]) -> void
10
14
  def self.init(include: [], exclude: [])
15
+ require "strict_ivars/patch_eval"
16
+
17
+ CONFIG.include(*include)
18
+ CONFIG.exclude(*exclude)
19
+
11
20
  RequireHooks.source_transform(
12
21
  patterns: include,
13
22
  exclude_patterns: exclude
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.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Drapper
@@ -47,6 +47,8 @@ files:
47
47
  - LICENSE.txt
48
48
  - README.md
49
49
  - lib/strict_ivars.rb
50
+ - lib/strict_ivars/configuration.rb
51
+ - lib/strict_ivars/patch_eval.rb
50
52
  - lib/strict_ivars/processor.rb
51
53
  - lib/strict_ivars/version.rb
52
54
  homepage: https://github.com/joeldrapper/strict_ivars