aye_var 0.1.0 → 0.1.1

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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -5
  3. data/lib/aye_var/version.rb +1 -1
  4. data/lib/aye_var.rb +116 -28
  5. metadata +15 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13bedf4da163ee9b20c83ed235f78825ced3e3a6547abc98af2de5e08d06401c
4
- data.tar.gz: 4cbc787d135c70ea6c1b725ad01af3c2be374959fb1a52667f9db6271e458b32
3
+ metadata.gz: 2e1eb735e2846d27610ccd41568c9022de487d62ccbf2c9d212af0019c72e1c9
4
+ data.tar.gz: 0ceb88f7fe73970d8c7c47d15671ecc2fc8350a8c05e29eed55d4ce6935828bd
5
5
  SHA512:
6
- metadata.gz: eb4d35e722d2f31d4953a03687421c18b1aea499e69e77ef614285622e41e8ab23c0422b6649dfad1e54066d77362ccad1a5851c3f2a9a1a7dafe6bf44fc877f
7
- data.tar.gz: bd303daa537463d610e8b37b356e3bc44e8bd46e4e4734acc58b24ca3a262748ae39e2169cbe328b3daddf4bd84767d94514fd6ed7ebdd54aa1ad980da4a8a4a
6
+ metadata.gz: ac42f044cd02865cd63d7194b76bbe5111da98bbdf9286ebc1975ff460bad95c22a6070c29238a2c2fa6ffdca2597f8f28aad1e0dfd7a3235c6051e635d16fc1
7
+ data.tar.gz: 52673c9d24958003fc168ddf02075332aac4d3275c70e73d0d3d86aca54aa6bc17ae2226cbb0c75df8d5537f0f3f208b0ce980c49b3f23160cdab75440f56ee9
data/README.md CHANGED
@@ -1,14 +1,22 @@
1
- ## AyeVar
1
+ # AyeVar 🏴‍☠️
2
2
 
3
- Note, this gem is very new and experimental. The API will probably change.
3
+ Arrr, this gem be mighty fresh an’ experimental, me hearties! Th’ API be likely to shift with th’ tides.
4
4
 
5
- Add this gem to your gemfile.
5
+ ## What does it do?
6
+
7
+ It prevents ye from usin’ undefined instance variables by transformin’ yer code as it be loaded, savvy?
8
+
9
+ Instance variables must be declared in yer object’s initializer (even if they be initially set to `nil`). Only then can ye access ’em in th’ rest o’ yer code, ye scurvy dog!
10
+
11
+ ## Setup
12
+
13
+ Add this treasure to yer gemfile, arr!
6
14
 
7
15
  ```ruby
8
16
  gem "aye_var", require: false
9
17
  ```
10
18
 
11
- Then require and initialize it in your app as early as possible. If youre using Bootsnap, it should be right after Bootsnap.
19
+ Then require an’ initialize it in yer vessel as early as possible, ye hear? If ye be usin’ Bootsnap, it should be right after Bootsnap, or I’ll make ye walk th’ plank!
12
20
 
13
21
  ```ruby
14
22
  require "aye_var"
@@ -16,4 +24,4 @@ require "aye_var"
16
24
  AyeVar.init(include: ["#{Dir.pwd}/**/*"])
17
25
  ```
18
26
 
19
- You can pass in an array of globs to `include:` and `exclude:`.
27
+ Ye can pass in an array o’ globs to `include:` an’ `exclude:`, ye bilge rat!
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AyeVar
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
data/lib/aye_var.rb CHANGED
@@ -1,77 +1,165 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "require-hooks/setup"
4
+ require "prism"
4
5
 
5
6
  module AyeVar
7
+ METHODS_PERMITTED_FOR_DEFINITION = Set[:initialize, :setup].freeze
8
+
9
+ NameError = Class.new(::NameError)
10
+
11
+ def self.init(include: [], exclude: [])
12
+ RequireHooks.source_transform(patterns: include, exclude_patterns: exclude) do |path, source|
13
+ source ||= File.read(path)
14
+ Processor.call(source)
15
+ end
16
+ end
17
+
6
18
  class Processor < Prism::Visitor
7
19
  def self.call(source)
8
- object = new(source)
9
- object.visit(Prism.parse(source).value)
20
+ visitor = new
21
+ visitor.visit(Prism.parse(source).value)
10
22
 
11
23
  buffer = source.dup
12
24
 
13
- object.ivars.sort_by(&:first).reverse_each do |offset, action, name|
25
+ visitor.annotations.sort_by(&:first).reverse_each do |offset, action, name|
14
26
  case action
15
27
  when :start
16
28
  buffer.insert(offset, "((raise ::AyeVar::NameError.new('Undefined instance variable #{name}') unless defined?(#{name}));")
17
29
  when :end
18
30
  buffer.insert(offset, ")")
31
+ else
32
+ raise "Invalid annotation"
19
33
  end
20
34
  end
21
35
 
22
36
  buffer
23
37
  end
24
38
 
25
- def initialize(source)
26
- @initializer = false
27
- @ivars = []
39
+ def initialize
40
+ @definition_context = true
41
+ @this = nil
42
+ @context = nil
43
+ @annotations = []
44
+ end
45
+
46
+ attr_reader :annotations
47
+
48
+ def visit_class_node(node)
49
+ new_context(node) { super }
50
+ end
51
+
52
+ def visit_module_node(node)
53
+ new_context(node) { super }
28
54
  end
29
55
 
30
- attr_reader :ivars
56
+ def visit_block_node(node)
57
+ new_context(node) { super }
58
+ end
59
+
60
+ def visit_singleton_class_node(node)
61
+ new_context(node) { super }
62
+ end
31
63
 
32
64
  def visit_def_node(node)
33
- if node.name == :initialize
34
- begin
35
- @initializer = true
65
+ parent = @this
66
+
67
+ new_context(node) do
68
+ if METHODS_PERMITTED_FOR_DEFINITION.include?(node.name) || Prism::SelfNode === node.receiver || !(Prism::ClassNode === parent)
36
69
  super
37
- ensure
38
- @initializer = false
70
+ else
71
+ prevent_definitions { super }
39
72
  end
40
- else
41
- super
42
73
  end
43
74
  end
44
75
 
76
+ def visit_if_node(node)
77
+ visit(node.predicate)
78
+
79
+ branch { visit(node.statements) }
80
+ branch { visit(node.subsequent) }
81
+ end
82
+
83
+ def visit_case_node(node)
84
+ visit(node.predicate)
85
+
86
+ node.conditions.each do |condition|
87
+ branch { visit(condition) }
88
+ end
89
+
90
+ branch { visit(node.else_clause) }
91
+ end
92
+
45
93
  def visit_instance_variable_read_node(node)
46
- location = node.location
47
94
  name = node.name
48
95
 
49
- @ivars << [location.start_character_offset, :start, name]
50
- @ivars << [location.end_character_offset, :end, name]
96
+ unless context.include?(name)
97
+ location = node.location
98
+
99
+ context << name
100
+
101
+ @annotations << [location.start_character_offset, :start, name]
102
+ @annotations << [location.end_character_offset, :end, name]
103
+ end
104
+
51
105
  super
52
106
  end
53
107
 
54
108
  def visit_instance_variable_write_node(node)
55
- unless @initializer
109
+ name = node.name
110
+
111
+ unless @definition_context || context.include?(name)
56
112
  location = node.location
57
- name = node.name
58
113
 
59
- @ivars << [location.start_character_offset, :start, name]
60
- @ivars << [location.end_character_offset, :end, name]
114
+ context << name
115
+
116
+ @annotations << [location.start_character_offset, :start, name]
117
+ @annotations << [location.end_character_offset, :end, name]
61
118
  end
62
119
 
63
120
  super
64
121
  end
65
- end
66
122
 
67
- private_constant :Processor
123
+ private def new_context(this)
124
+ original_this = @this
125
+ original_context = @context
68
126
 
69
- NameError = Class.new(::NameError)
127
+ @this = this
128
+ @context = Set[]
70
129
 
71
- def self.init(include: [], exclude: [])
72
- RequireHooks.source_transform(patterns: include, exclude_patterns: exclude) do |path, source|
73
- source ||= File.read(path)
74
- Processor.call(source)
130
+ begin
131
+ yield
132
+ ensure
133
+ @this = original_this
134
+ @context = original_context
135
+ end
136
+ end
137
+
138
+ private def branch
139
+ original_context = @context
140
+ @context = original_context.dup
141
+
142
+ begin
143
+ yield
144
+ ensure
145
+ @context = original_context
146
+ end
147
+ end
148
+
149
+ # The current context on the stack
150
+ private def context
151
+ @context
152
+ end
153
+
154
+ private def prevent_definitions
155
+ original_definition_context = @definition_context
156
+
157
+ begin
158
+ @definition_context = false
159
+ yield
160
+ ensure
161
+ @definition_context = original_definition_context
162
+ end
75
163
  end
76
164
  end
77
165
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aye_var
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Drapper
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: prism
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
26
40
  description: Raise an exception when using undefined instance variables.
27
41
  email:
28
42
  - joel@drapper.me