abstriker 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9084bf87aeeb1ffa9f9ec57310d709c57f30ad039154c384720793eea694c632
4
- data.tar.gz: 6ee676d5a9a1229d38ed3a4ae2b7a4e1fe07cd3f5b1100954e0f42cdd1cb9e66
3
+ metadata.gz: d17bb11d7fe2be3f6fe021b859d935b7e7b8a0e45b9b33a7d01f89bef970bc97
4
+ data.tar.gz: 9c09e42b619315dc52f7bb17fb4a204018587dbfeff01188476854306a7db97a
5
5
  SHA512:
6
- metadata.gz: 01d1026f053c0fe15266eeeba49e0795f87537e6393a0c326e8ccb6e26756792e087705a0a121c442494d46253b4eb985a668e7408077620b19faa999af30673
7
- data.tar.gz: a5f838cca75e71629eb62645a78ae851962c361290d448ec5d14bec066a6dfc2d45be57e4cbf917920ce800ae6e9f6cb77e9fa511ded96793ef86c263281fb8e
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
- ### Caution
53
-
54
- Must not call `include` or `extend` abstracted module outer class definition.
55
+ ### Examples
55
56
 
56
- ex.
57
+ #### include module
57
58
 
58
59
  ```ruby
59
- module A1
60
+ module B1
60
61
  extend Abstriker
61
62
 
62
63
  abstract def foo
63
64
  end
64
65
  end
65
66
 
66
- class A2;
67
- end
67
+ class B2
68
+ include B1
69
+ end # => raise
68
70
 
69
- A2.include(A1) # => Don't do this
71
+ Module.new do
72
+ include B1
73
+ end # => raise
70
74
  ```
71
75
 
72
- This case leaves enabled TracePoint.
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 B1
78
+ module A1
81
79
  extend Abstriker
82
80
 
83
81
  abstract def foo
84
82
  end
85
83
  end
86
84
 
87
- class B2
88
- include B1
89
- end # => raise
85
+ class A2;
86
+ end
90
87
 
91
- Module.new do
92
- include B1
93
- end # => raise
88
+ A2.include(A1) # => raise
94
89
  ```
95
90
 
96
91
  #### extend module
@@ -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 detect_event_type
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
- block_count = block_count_offset
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
- target_b_return_event = t_self == klass && t.event == :b_return && block_count.zero?
95
- target_c_return_event = t_self == Class && t.event == :c_return && t.method_id == :new
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, block_count_offset = 0)
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(*event_type) do |t|
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
- target_b_return_event = t_self == klass && t.event == :b_return && block_count.zero?
135
- target_c_return_event = t_self == Class && t.event == :c_return && t.method_id == :new
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
- check_abstract_methods(base, 1)
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
- check_abstract_singleton_methods(base, 1)
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
@@ -1,3 +1,3 @@
1
1
  module Abstriker
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abstriker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - joker1007