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 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