abstriker 0.1.2 → 0.1.3
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 +17 -22
- data/lib/abstriker.rb +104 -46
- data/lib/abstriker/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d17bb11d7fe2be3f6fe021b859d935b7e7b8a0e45b9b33a7d01f89bef970bc97
|
4
|
+
data.tar.gz: 9c09e42b619315dc52f7bb17fb4a204018587dbfeff01188476854306a7db97a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cded67dbc8f8034e071627891f2feea0a15232617ba0844ec35914bf5daaa49a7a769d9dbfc85bff39f47dcffd672402c27bfb4ce09f45c30c78dfd33cb1682d
|
7
|
+
data.tar.gz: 1a532f0dad983bea20245c3d640c37f80327a649a31a718666394f9288c43176aa809471bc0723cd62050b3a0f2124c2279f549346f03c7f341c2b0ba319c960
|
data/README.md
CHANGED
@@ -7,6 +7,9 @@ This gem adds `abstract` syntax. that is similar to Java's one.
|
|
7
7
|
If subclass does not implement `abstract` method, raise `Abstriker::NotImplementedError`.
|
8
8
|
`Abstriker::NotImplementedError` is curently subclass of `::NotImplementedError`.
|
9
9
|
|
10
|
+
This gem is pseudo static code analyzer by `TracePoint` and `Ripper`.
|
11
|
+
it detect abstract violation when class(module) is defined, not runtime.
|
12
|
+
|
10
13
|
## Installation
|
11
14
|
|
12
15
|
Add this line to your application's Gemfile:
|
@@ -49,48 +52,40 @@ end # => raise
|
|
49
52
|
If you want to disable Abstriker, write `Abstriker.disable = true` at first line.
|
50
53
|
If Abstriker is disabled, TracePoint never runs, and so there is no overhead of VM instruction.
|
51
54
|
|
52
|
-
###
|
53
|
-
|
54
|
-
Must not call `include` or `extend` abstracted module outer class definition.
|
55
|
+
### Examples
|
55
56
|
|
56
|
-
|
57
|
+
#### include module
|
57
58
|
|
58
59
|
```ruby
|
59
|
-
module
|
60
|
+
module B1
|
60
61
|
extend Abstriker
|
61
62
|
|
62
63
|
abstract def foo
|
63
64
|
end
|
64
65
|
end
|
65
66
|
|
66
|
-
class
|
67
|
-
|
67
|
+
class B2
|
68
|
+
include B1
|
69
|
+
end # => raise
|
68
70
|
|
69
|
-
|
71
|
+
Module.new do
|
72
|
+
include B1
|
73
|
+
end # => raise
|
70
74
|
```
|
71
75
|
|
72
|
-
|
73
|
-
It is very high overhead.
|
74
|
-
|
75
|
-
### Examples
|
76
|
-
|
77
|
-
#### include module
|
78
|
-
|
76
|
+
#### include module outer class definition
|
79
77
|
```ruby
|
80
|
-
module
|
78
|
+
module A1
|
81
79
|
extend Abstriker
|
82
80
|
|
83
81
|
abstract def foo
|
84
82
|
end
|
85
83
|
end
|
86
84
|
|
87
|
-
class
|
88
|
-
|
89
|
-
end # => raise
|
85
|
+
class A2;
|
86
|
+
end
|
90
87
|
|
91
|
-
|
92
|
-
include B1
|
93
|
-
end # => raise
|
88
|
+
A2.include(A1) # => raise
|
94
89
|
```
|
95
90
|
|
96
91
|
#### extend module
|
data/lib/abstriker.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "abstriker/version"
|
2
2
|
require "set"
|
3
|
+
require "ripper"
|
3
4
|
|
4
5
|
module Abstriker
|
5
6
|
class NotImplementedError < NotImplementedError
|
@@ -12,6 +13,35 @@ module Abstriker
|
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
16
|
+
class SexpTraverser
|
17
|
+
def initialize(sexp)
|
18
|
+
@sexp = sexp
|
19
|
+
end
|
20
|
+
|
21
|
+
def traverse(current_sexp = nil, parent = nil, &block)
|
22
|
+
sexp = current_sexp || @sexp
|
23
|
+
first = sexp[0]
|
24
|
+
if first.is_a?(Symbol) # node
|
25
|
+
yield sexp, parent
|
26
|
+
args = Ripper::PARSER_EVENT_TABLE[first]
|
27
|
+
return if args.nil? || args.zero?
|
28
|
+
|
29
|
+
args.times do |i|
|
30
|
+
param = sexp[i + 1]
|
31
|
+
if param.is_a?(Array)
|
32
|
+
traverse(param, sexp, &block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
else # array
|
36
|
+
sexp.each do |n|
|
37
|
+
if n.is_a?(Array)
|
38
|
+
traverse(n, sexp, &block)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
15
45
|
@disable = false
|
16
46
|
|
17
47
|
def self.disable=(v)
|
@@ -30,6 +60,10 @@ module Abstriker
|
|
30
60
|
@abstract_methods ||= {}
|
31
61
|
end
|
32
62
|
|
63
|
+
def self.sexps
|
64
|
+
@sexps ||= {}
|
65
|
+
end
|
66
|
+
|
33
67
|
def self.extended(base)
|
34
68
|
base.extend(SyntaxMethods)
|
35
69
|
base.singleton_class.extend(SyntaxMethods)
|
@@ -51,49 +85,42 @@ module Abstriker
|
|
51
85
|
module HookBase
|
52
86
|
private
|
53
87
|
|
54
|
-
def
|
55
|
-
callers = caller_locations(2, 3)
|
56
|
-
if callers[0].label == "inherited"
|
57
|
-
caller_info = callers[2]
|
58
|
-
if caller_info.label.match?(/initialize/) || caller_info.label.match?(/new/)
|
59
|
-
[:c_return, :b_call, :b_return, :raise]
|
60
|
-
else
|
61
|
-
[:end, :raise]
|
62
|
-
end
|
63
|
-
elsif callers[0].label == "included" || callers[0].label
|
64
|
-
caller_info = callers[2]
|
65
|
-
if caller_info.label.match?(/<class/) || caller_info.label.match?(/<module/)
|
66
|
-
[:end, :raise]
|
67
|
-
else
|
68
|
-
[:c_return, :b_call, :b_return, :raise]
|
69
|
-
end
|
70
|
-
else
|
71
|
-
raise "Not detect event_type"
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def check_abstract_methods(klass, block_count_offset = 0)
|
88
|
+
def check_abstract_methods(klass)
|
76
89
|
return if Abstriker.disabled?
|
77
90
|
|
78
|
-
event_type = detect_event_type
|
79
|
-
|
80
91
|
unless klass.instance_variable_get("@__abstract_trace_point")
|
81
|
-
|
82
|
-
|
83
|
-
tp = TracePoint.trace(*event_type) do |t|
|
92
|
+
tp = TracePoint.trace(:end, :c_return, :raise) do |t|
|
84
93
|
if t.event == :raise
|
85
94
|
tp.disable
|
86
95
|
next
|
87
96
|
end
|
88
97
|
|
89
|
-
block_count += 1 if t.event == :b_call
|
90
|
-
block_count -= 1 if t.event == :b_return
|
91
|
-
|
92
98
|
t_self = t.self
|
99
|
+
|
100
|
+
target_outer_include = false
|
101
|
+
if t.event == :c_return && t_self == klass && t.method_id == :include
|
102
|
+
traverser = SexpTraverser.new(Abstriker.sexps[t.path])
|
103
|
+
traverser.traverse do |n, parent|
|
104
|
+
if n[0] == :@ident && n[1] == "include" && n[2][0] == t.lineno
|
105
|
+
if parent[0] == :command || parent[0] == :fcall
|
106
|
+
# include Mod
|
107
|
+
elsif parent[0] == :command_call || parent[0] == :call
|
108
|
+
if parent[1][0] == :var_ref && parent[1][1][0] == :@kw && parent[1][1][1] == "self"
|
109
|
+
# self.include Mod
|
110
|
+
else
|
111
|
+
# unknown case
|
112
|
+
target_outer_include = true
|
113
|
+
end
|
114
|
+
else
|
115
|
+
target_outer_include = true
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
93
121
|
target_end_event = t_self == klass && t.event == :end
|
94
|
-
|
95
|
-
|
96
|
-
if target_end_event || target_b_return_event || target_c_return_event
|
122
|
+
target_c_return_event = (t_self == Class || t_self == Module) && t.event == :c_return && t.method_id == :new
|
123
|
+
if target_end_event || target_c_return_event || target_outer_include
|
97
124
|
klass.ancestors.drop(1).each do |mod|
|
98
125
|
Abstriker.abstract_methods[mod]&.each do |fmeth_name|
|
99
126
|
meth = klass.instance_method(fmeth_name)
|
@@ -112,28 +139,43 @@ module Abstriker
|
|
112
139
|
end
|
113
140
|
end
|
114
141
|
|
115
|
-
def check_abstract_singleton_methods(klass
|
142
|
+
def check_abstract_singleton_methods(klass)
|
116
143
|
return if Abstriker.disabled?
|
117
144
|
|
118
|
-
event_type = detect_event_type
|
119
|
-
|
120
145
|
unless klass.instance_variable_get("@__abstract_singleton_trace_point")
|
121
|
-
block_count = block_count_offset
|
122
146
|
|
123
|
-
tp = TracePoint.trace(
|
147
|
+
tp = TracePoint.trace(:end, :c_return, :raise) do |t|
|
124
148
|
if t.event == :raise
|
125
149
|
tp.disable
|
126
150
|
next
|
127
151
|
end
|
128
152
|
|
129
|
-
block_count += 1 if t.event == :b_call
|
130
|
-
block_count -= 1 if t.event == :b_return
|
131
|
-
|
132
153
|
t_self = t.self
|
154
|
+
|
155
|
+
target_outer_extend = false
|
156
|
+
if t.event == :c_return && t_self == klass && t.method_id == :extend
|
157
|
+
traverser = SexpTraverser.new(Abstriker.sexps[t.path])
|
158
|
+
traverser.traverse do |n, parent|
|
159
|
+
if n[0] == :@ident && n[1] == "extend" && n[2][0] == t.lineno
|
160
|
+
if parent[0] == :command || parent[0] == :fcall
|
161
|
+
# extend Mod
|
162
|
+
elsif parent[0] == :command_call || parent[0] == :call
|
163
|
+
if parent[1][0] == :var_ref && parent[1][1][0] == :@kw && parent[1][1][1] == "self"
|
164
|
+
# self.extend Mod
|
165
|
+
else
|
166
|
+
# unknown case
|
167
|
+
target_outer_extend = true
|
168
|
+
end
|
169
|
+
else
|
170
|
+
target_outer_extend = true
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
133
176
|
target_end_event = t_self == klass && t.event == :end
|
134
|
-
|
135
|
-
|
136
|
-
if target_end_event || target_b_return_event || target_c_return_event
|
177
|
+
target_c_return_event = (t_self == Class || t_self == Module) && t.event == :c_return && t.method_id == :new
|
178
|
+
if target_end_event || target_c_return_event || target_outer_extend
|
137
179
|
klass.singleton_class.ancestors.drop(1).each do |mod|
|
138
180
|
Abstriker.abstract_methods[mod]&.each do |fmeth_name|
|
139
181
|
meth = klass.singleton_class.instance_method(fmeth_name)
|
@@ -170,11 +212,27 @@ module Abstriker
|
|
170
212
|
private
|
171
213
|
|
172
214
|
def included(base)
|
173
|
-
|
215
|
+
super
|
216
|
+
return if Abstriker.disabled?
|
217
|
+
|
218
|
+
caller_info = caller_locations(1, 1)[0]
|
219
|
+
|
220
|
+
unless Abstriker.sexps[caller_info.absolute_path]
|
221
|
+
Abstriker.sexps[caller_info.absolute_path] ||= Ripper.sexp(File.read(caller_info.absolute_path))
|
222
|
+
end
|
223
|
+
check_abstract_methods(base)
|
174
224
|
end
|
175
225
|
|
176
226
|
def extended(base)
|
177
|
-
|
227
|
+
super
|
228
|
+
return if Abstriker.disabled?
|
229
|
+
|
230
|
+
caller_info = caller_locations(1, 1)[0]
|
231
|
+
|
232
|
+
unless Abstriker.sexps[caller_info.absolute_path]
|
233
|
+
Abstriker.sexps[caller_info.absolute_path] ||= Ripper.sexp(File.read(caller_info.absolute_path))
|
234
|
+
end
|
235
|
+
check_abstract_singleton_methods(base)
|
178
236
|
end
|
179
237
|
end
|
180
238
|
end
|
data/lib/abstriker/version.rb
CHANGED