zombie-killer 0.4 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/NEWS.md +26 -0
- data/bin/count_method_calls +9 -6
- data/bin/zk +4 -1
- data/lib/zombie_killer.rb +2 -0
- data/lib/zombie_killer/code_histogram.rb +5 -2
- data/lib/zombie_killer/eager_rewriter.rb +107 -24
- data/lib/zombie_killer/killer.rb +3 -1
- data/lib/zombie_killer/niceness.rb +5 -3
- data/lib/zombie_killer/node_type_counter.rb +4 -1
- data/lib/zombie_killer/rewriter.rb +15 -14
- data/lib/zombie_killer/rule.rb +64 -0
- data/lib/zombie_killer/variable_scope.rb +2 -0
- data/lib/zombie_killer/version.rb +3 -1
- data/spec/rspec_renderer.rb +19 -21
- data/spec/rule_spec.rb +63 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/zombie_killer_spec.rb +3 -1
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b796ce723f6b571b0e16446ca9c2edce61ed9f92c4a71d4f095f9f87de8175b5
|
4
|
+
data.tar.gz: e4e254e3a0b2f958e29a66e22a71ed4a87e13f7be1deb1becfc434dfff9a4134
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4290278f9a662ba0ac56781e0fdfe7f783d2632d9db446a0deb9ef69f5901f7491f8850459155c4bd18f5ab9ed89703c662dcec268be35442351f535cb8d1b70
|
7
|
+
data.tar.gz: c3236fc0b1553aea76e960d49ec84f41f2fcdc91a284a728633836484e31c3475e9825d5e13dbe6921f426e09cf517099e78452ee44cf96255cac6c4ffd19e7b
|
data/NEWS.md
CHANGED
@@ -2,6 +2,32 @@
|
|
2
2
|
|
3
3
|
## unreleased
|
4
4
|
|
5
|
+
## 0.5, 2018-11-08
|
6
|
+
|
7
|
+
- zk: find all *.rb files in subdirectories if an argument is a directory
|
8
|
+
- Eager mode replacements:
|
9
|
+
```rb
|
10
|
+
Builtins.size(foo)
|
11
|
+
if Builtins.size(bar) == 0 || Builtins.size(qux) > 0
|
12
|
+
Builtins.sformat("... %1 ...", val)
|
13
|
+
end
|
14
|
+
|
15
|
+
a = a + foo
|
16
|
+
@b = @b * bar
|
17
|
+
@@c = @@c - qux
|
18
|
+
```
|
19
|
+
becomes
|
20
|
+
```rb
|
21
|
+
foo.size
|
22
|
+
if bar.empty? || !qux.empty?
|
23
|
+
"... #{val} ..."
|
24
|
+
end
|
25
|
+
|
26
|
+
a += foo
|
27
|
+
@b *= bar
|
28
|
+
@@c -= baz
|
29
|
+
```
|
30
|
+
|
5
31
|
## 0.4, 2018-11-02
|
6
32
|
|
7
33
|
- Added zk -e, EagerRewriter (don't care about niceness, replace all)
|
data/bin/count_method_calls
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
#! /usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
usage = <<'EOT'
|
3
5
|
Usage:
|
4
6
|
count_method_calls PATTERN RUBY_FILE
|
@@ -13,6 +15,7 @@ EOT
|
|
13
15
|
require "parser"
|
14
16
|
require "parser/current"
|
15
17
|
|
18
|
+
# Count occurrences of particular methods
|
16
19
|
class MethodCounter < Parser::AST::Processor
|
17
20
|
def initialize(pattern)
|
18
21
|
@pattern = pattern
|
@@ -29,11 +32,11 @@ class MethodCounter < Parser::AST::Processor
|
|
29
32
|
def on_send(node)
|
30
33
|
super
|
31
34
|
receiver, message = *node
|
32
|
-
if receiver.nil?
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
35
|
+
method = if receiver.nil?
|
36
|
+
message.to_s
|
37
|
+
else
|
38
|
+
"#{const_to_s(receiver)}.#{message}"
|
39
|
+
end
|
37
40
|
# extglob: {brace,alternatives}
|
38
41
|
@count += 1 if File.fnmatch(@pattern, method, File::FNM_EXTGLOB)
|
39
42
|
end
|
@@ -49,7 +52,7 @@ class MethodCounter < Parser::AST::Processor
|
|
49
52
|
"#{const_to_s(parent)}.#{name}"
|
50
53
|
end
|
51
54
|
else
|
52
|
-
"%"
|
55
|
+
"%" # a non-identifier placeholder for "expression"
|
53
56
|
end
|
54
57
|
end
|
55
58
|
end
|
data/bin/zk
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require "docopt"
|
4
5
|
|
@@ -25,8 +26,10 @@ begin
|
|
25
26
|
killer = ZombieKiller.new(eager: options["--eager"])
|
26
27
|
|
27
28
|
files = options["FILES"]
|
28
|
-
files << "
|
29
|
+
files << "." if files.empty?
|
29
30
|
files = files.flat_map do |pattern|
|
31
|
+
pattern += "/**/*.rb" if File.directory?(pattern)
|
32
|
+
|
30
33
|
if pattern.include? "*"
|
31
34
|
Dir[pattern]
|
32
35
|
else
|
data/lib/zombie_killer.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Keep track of the count of occurences of things (in code)
|
1
4
|
class CodeHistogram
|
2
5
|
attr_reader :counts
|
3
6
|
|
@@ -32,7 +35,7 @@ class CodeHistogram
|
|
32
35
|
end
|
33
36
|
|
34
37
|
def merge!(other)
|
35
|
-
counts.merge!(other.counts) do |
|
38
|
+
counts.merge!(other.counts) do |_key, count, other_count|
|
36
39
|
count + other_count
|
37
40
|
end
|
38
41
|
end
|
@@ -42,7 +45,7 @@ class CodeHistogram
|
|
42
45
|
def invert_hash_preserving_duplicates(h)
|
43
46
|
ih = {}
|
44
47
|
h.each do |k, v|
|
45
|
-
ih[v] = [] unless ih.
|
48
|
+
ih[v] = [] unless ih.key?(v)
|
46
49
|
ih[v] << k
|
47
50
|
end
|
48
51
|
ih
|
@@ -1,9 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "unparser"
|
2
4
|
require "set"
|
3
5
|
|
6
|
+
require "zombie_killer/rule"
|
7
|
+
|
4
8
|
# Rewrite Zombies with their idiomatic replacements
|
5
9
|
class EagerRewriter < Parser::TreeRewriter
|
6
|
-
|
10
|
+
def self.s(name, *children)
|
11
|
+
Parser::AST::Node.new(name, children)
|
12
|
+
end
|
13
|
+
|
14
|
+
def s(name, *children)
|
15
|
+
self.class.s(name, *children)
|
16
|
+
end
|
17
|
+
|
18
|
+
OPS = s(:const, nil, :Ops)
|
19
|
+
BUILTINS = s(:const, nil, :Builtins)
|
20
|
+
Arg = Rule::Arg
|
21
|
+
|
22
|
+
@rules = {}
|
23
|
+
class << self
|
24
|
+
attr_reader :rules
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.r(**kwargs)
|
28
|
+
rule = Rule.new(**kwargs)
|
29
|
+
type = rule.from.type
|
30
|
+
@rules[type] ||= []
|
31
|
+
@rules[type] << rule
|
32
|
+
end
|
33
|
+
|
34
|
+
[
|
35
|
+
[:lvasgn, :lvar], # a = b
|
36
|
+
[:ivasgn, :ivar], # @a = @b
|
37
|
+
[:cvasgn, :cvar], # @@a = @@b
|
38
|
+
].each do |xvasgn, xvar|
|
39
|
+
[:+, :-, :*].each do |asop|
|
40
|
+
r from: s(xvasgn,
|
41
|
+
Arg,
|
42
|
+
s(:send, s(xvar, Arg), asop, Arg)), # @ARG1 = @ARG2 + ARG3
|
43
|
+
to: ->(a, b, c) do # rubocop:disable Style/Lambda
|
44
|
+
if a == b
|
45
|
+
s(:op_asgn, s(xvasgn, a), asop, c) # @ARG1 += ARG3
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
7
50
|
|
8
51
|
INFIX = {
|
9
52
|
add: :+,
|
@@ -21,36 +64,76 @@ class EagerRewriter < Parser::TreeRewriter
|
|
21
64
|
greater_or_equal: :>=
|
22
65
|
}.freeze
|
23
66
|
|
24
|
-
|
25
|
-
|
67
|
+
INFIX.each do |prefix, infix|
|
68
|
+
r from: s(:send, OPS, prefix, Arg, Arg), # Ops.add(Arg, ARG2)
|
69
|
+
to: ->(a, b) { s(:send, a, infix, b) } # Arg + ARG2
|
26
70
|
end
|
27
71
|
|
28
|
-
|
29
|
-
|
30
|
-
|
72
|
+
r from: s(:send, BUILTINS, :size, Arg), # Builtins.size(Arg)
|
73
|
+
to: ->(a) { s(:send, a, :size) } # Arg.size
|
74
|
+
|
75
|
+
r from: s(:send, s(:send, Arg, :size), :>, s(:int, 0)), # Arg.size > 0
|
76
|
+
to: ->(a) { s(:send, s(:send, a, :empty?), :!) } # !Arg.empty?
|
77
|
+
|
78
|
+
r from: s(:send, s(:send, Arg, :size), :!=, s(:int, 0)), # Arg.size != 0
|
79
|
+
to: ->(a) { s(:send, s(:send, a, :empty?), :!) } # !Arg.empty?
|
80
|
+
|
81
|
+
r from: s(:send, s(:send, Arg, :size), :==, s(:int, 0)), # Arg.size == 0
|
82
|
+
to: ->(a) { s(:send, a, :empty?) } # Arg.empty?
|
83
|
+
|
84
|
+
r from: s(:send, s(:send, Arg, :size), :<=, s(:int, 0)), # Arg.size <= 0
|
85
|
+
to: ->(a) { s(:send, a, :empty?) } # Arg.empty?
|
86
|
+
|
87
|
+
r from: s(:send, s(:send, Arg, :size), :<, s(:int, 1)), # Arg.size < 1
|
88
|
+
to: ->(a) { s(:send, a, :empty?) } # Arg.empty?
|
89
|
+
|
90
|
+
def self.sformat_replacement1(format_literal, value)
|
91
|
+
verbatims = format_literal.split("%1", -1)
|
92
|
+
return nil unless verbatims.size == 2
|
93
|
+
s(:dstr, s(:str, verbatims[0]), value, s(:str, verbatims[1]))
|
31
94
|
end
|
32
95
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
96
|
+
r from: s(:send, BUILTINS, :sformat, s(:str, Arg), Arg), # Builtins.sformat("...", val)
|
97
|
+
to: ->(fmt, val) { sformat_replacement1(fmt, val) }
|
98
|
+
|
99
|
+
# Does not improve readability much, fails on nil. Use foo&.each ?
|
100
|
+
# r from: s(:send, BUILTINS, :foreach, Arg),
|
101
|
+
# to: ->(a) { s(:send, a, :each) }
|
102
|
+
|
103
|
+
def unparser_sanitize(code_s)
|
104
|
+
# unparser converts "foo#{bar}baz"
|
105
|
+
# into "#{"foo"}#{bar}#{"baz"}"
|
106
|
+
# so this undoes the escaping of the litetrals
|
107
|
+
code_s.gsub(/
|
108
|
+
\#
|
109
|
+
\{"
|
110
|
+
(
|
111
|
+
[^"#]*
|
112
|
+
)
|
113
|
+
"\}
|
114
|
+
/x,
|
115
|
+
'\1')
|
40
116
|
end
|
41
117
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
118
|
+
def replace_node(old_node, new_node)
|
119
|
+
# puts "OLD #{old_node.inspect}"
|
120
|
+
# puts "NEW #{new_node.inspect}"
|
121
|
+
source_range = old_node.loc.expression
|
122
|
+
unp = Unparser.unparse(new_node)
|
123
|
+
unp = unparser_sanitize(unp)
|
124
|
+
# puts "UNP #{unp.inspect}"
|
125
|
+
replace(source_range, unp)
|
126
|
+
new_node
|
51
127
|
end
|
52
128
|
|
53
|
-
def
|
54
|
-
|
129
|
+
def process(node)
|
130
|
+
node = super(node)
|
131
|
+
return if node.nil?
|
132
|
+
trules = self.class.rules.fetch(node.type, [])
|
133
|
+
trules.find do |r|
|
134
|
+
replacement = r.match(node)
|
135
|
+
node = replace_node(node, replacement) if replacement
|
136
|
+
end
|
137
|
+
node
|
55
138
|
end
|
56
139
|
end
|
data/lib/zombie_killer/killer.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "set"
|
2
4
|
|
3
5
|
# Niceness of a node means that it cannot be nil.
|
@@ -33,12 +35,12 @@ module Niceness
|
|
33
35
|
# These are global, called with a nil receiver
|
34
36
|
NICE_GLOBAL_METHODS = {
|
35
37
|
# message, number of arguments
|
36
|
-
:
|
38
|
+
_: 1
|
37
39
|
}.freeze
|
38
40
|
|
39
41
|
NICE_OPERATORS = {
|
40
42
|
# message, number of arguments (other than receiver)
|
41
|
-
:+ => 1
|
43
|
+
:+ => 1
|
42
44
|
}.freeze
|
43
45
|
|
44
46
|
def nice_send(node)
|
@@ -51,7 +53,7 @@ module Niceness
|
|
51
53
|
return false unless nice(receiver)
|
52
54
|
arity = NICE_OPERATORS.fetch(message, -1)
|
53
55
|
end
|
54
|
-
|
56
|
+
args.size == arity && args.all? { |a| nice(a) }
|
55
57
|
end
|
56
58
|
|
57
59
|
def nice_begin(node)
|
@@ -1,7 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "parser"
|
2
4
|
|
3
5
|
require_relative "code_histogram"
|
4
6
|
|
7
|
+
# Count node types (:send, :lvar etc)
|
5
8
|
class NodeTypeCounter < Parser::Rewriter
|
6
9
|
attr_reader :node_types
|
7
10
|
|
@@ -20,7 +23,7 @@ class NodeTypeCounter < Parser::Rewriter
|
|
20
23
|
parser = Parser::CurrentRuby.new
|
21
24
|
buffer = Parser::Source::Buffer.new(@filename)
|
22
25
|
buffer.read
|
23
|
-
ast
|
26
|
+
ast = parser.parse(buffer)
|
24
27
|
|
25
28
|
process(ast)
|
26
29
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "parser"
|
2
4
|
require "parser/current"
|
3
5
|
require "set"
|
@@ -175,10 +177,10 @@ class ZombieKillerRewriter < Parser::Rewriter
|
|
175
177
|
end
|
176
178
|
|
177
179
|
# def on_unless
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
180
|
+
# Does not exist.
|
181
|
+
# `unless` is parsed as an `if` with then_body and else_body swapped.
|
182
|
+
# Compare with `while` and `until` which cannot do that and thus need
|
183
|
+
# distinct node types.
|
182
184
|
# end
|
183
185
|
|
184
186
|
def on_case(node)
|
@@ -222,7 +224,7 @@ class ZombieKillerRewriter < Parser::Rewriter
|
|
222
224
|
|
223
225
|
def on_send(node)
|
224
226
|
super
|
225
|
-
if
|
227
|
+
if call?(node, :Ops, :add)
|
226
228
|
new_op = :+
|
227
229
|
|
228
230
|
_ops, _add, a, b = *node
|
@@ -232,13 +234,13 @@ class ZombieKillerRewriter < Parser::Rewriter
|
|
232
234
|
end
|
233
235
|
end
|
234
236
|
|
235
|
-
def on_block(
|
237
|
+
def on_block(_node)
|
236
238
|
# ignore body, clean slate
|
237
239
|
scope.clear
|
238
240
|
end
|
239
241
|
alias_method :on_for, :on_block
|
240
242
|
|
241
|
-
def on_while(
|
243
|
+
def on_while(_node)
|
242
244
|
# ignore both condition and body,
|
243
245
|
# with a simplistic scope we cannot handle them
|
244
246
|
|
@@ -279,33 +281,32 @@ class ZombieKillerRewriter < Parser::Rewriter
|
|
279
281
|
super
|
280
282
|
end
|
281
283
|
|
282
|
-
def on_ensure(
|
284
|
+
def on_ensure(_node)
|
283
285
|
# (:ensure, guarded-code, ensuring-code)
|
284
286
|
# guarded-code may be a :rescue or not
|
285
287
|
|
286
288
|
scope.clear
|
287
289
|
end
|
288
290
|
|
289
|
-
def on_retry(
|
291
|
+
def on_retry(_node)
|
290
292
|
# that makes the :rescue a loop, top-down data-flow fails
|
291
293
|
raise TooComplexToTranslateError
|
292
294
|
end
|
293
295
|
|
294
296
|
private
|
295
297
|
|
296
|
-
def
|
298
|
+
def call?(node, namespace, message)
|
297
299
|
n_receiver, n_message = *node
|
298
300
|
n_receiver && n_receiver.type == :const &&
|
299
|
-
n_receiver.children[0]
|
301
|
+
n_receiver.children[0].nil? &&
|
300
302
|
n_receiver.children[1] == namespace &&
|
301
303
|
n_message == message
|
302
304
|
end
|
303
305
|
|
304
306
|
def replace_node(old_node, new_node)
|
305
307
|
source_range = old_node.loc.expression
|
306
|
-
if
|
307
|
-
|
308
|
-
end
|
308
|
+
return if contains_comment?(source_range.source)
|
309
|
+
replace(source_range, Unparser.unparse(new_node))
|
309
310
|
end
|
310
311
|
|
311
312
|
def contains_comment?(string)
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ARG; end
|
4
|
+
class ARG1 < ARG; end
|
5
|
+
class ARG2 < ARG; end
|
6
|
+
class ARG3 < ARG; end
|
7
|
+
|
8
|
+
# Rewriting rule
|
9
|
+
class Rule
|
10
|
+
Placeholder = Struct.new(:name) do
|
11
|
+
def inspect
|
12
|
+
name
|
13
|
+
end
|
14
|
+
end
|
15
|
+
Arg = Placeholder.new("Arg")
|
16
|
+
Arg1 = Placeholder.new("Arg1")
|
17
|
+
|
18
|
+
# @return [AST::Node]
|
19
|
+
attr_reader :from
|
20
|
+
# @return [Proc]
|
21
|
+
attr_reader :to
|
22
|
+
|
23
|
+
def initialize(from:, to:)
|
24
|
+
@from = from
|
25
|
+
@to = to
|
26
|
+
end
|
27
|
+
|
28
|
+
def match(node)
|
29
|
+
captures = match2(from, node)
|
30
|
+
return unless captures
|
31
|
+
if to.respond_to? :call
|
32
|
+
to.call(*captures)
|
33
|
+
else
|
34
|
+
to
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return an array of captured values or nil
|
39
|
+
def match2(expected, actual)
|
40
|
+
# puts "M2 #{expected.inspect} #{actual.inspect}"
|
41
|
+
# p expected.class
|
42
|
+
# p actual.class
|
43
|
+
return [] if expected.nil? && actual.nil?
|
44
|
+
return nil if expected.nil? || actual.nil?
|
45
|
+
|
46
|
+
# if we're a node
|
47
|
+
case expected
|
48
|
+
when AST::Node
|
49
|
+
return nil if expected.type != actual.type
|
50
|
+
return nil if expected.children.size != actual.children.size
|
51
|
+
|
52
|
+
results = expected.children.zip(actual.children).map do |ec, ac|
|
53
|
+
match2(ec, ac)
|
54
|
+
end
|
55
|
+
# puts "#{results.inspect} for #{expected.inspect}"
|
56
|
+
results.flatten(1) if results.all?
|
57
|
+
when Rule::Arg
|
58
|
+
# puts "ARG #{actual.inspect}"
|
59
|
+
[actual]
|
60
|
+
else
|
61
|
+
expected == actual ? [] : nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/spec/rspec_renderer.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "redcarpet"
|
2
4
|
|
3
5
|
require_relative "../lib/zombie_killer"
|
@@ -46,9 +48,7 @@ class Describe
|
|
46
48
|
def render
|
47
49
|
parts = []
|
48
50
|
parts << "describe #{@description.inspect} do"
|
49
|
-
|
50
|
-
parts << Code.indent(@blocks.map(&:render).join("\n\n"))
|
51
|
-
end
|
51
|
+
parts << Code.indent(@blocks.map(&:render).join("\n\n")) unless blocks.empty?
|
52
52
|
parts << "end"
|
53
53
|
parts.join("\n")
|
54
54
|
end
|
@@ -79,7 +79,7 @@ class RSpecRenderer < Redcarpet::Render::Base
|
|
79
79
|
|
80
80
|
def paragraph(text)
|
81
81
|
if text =~ /^\*\*(.*)\*\*$/
|
82
|
-
@next_block_type =
|
82
|
+
@next_block_type = Regexp.last_match(1).downcase.to_sym
|
83
83
|
else
|
84
84
|
first_sentence = text.split(/\.(\s+|$)/).first
|
85
85
|
@description = first_sentence.sub(/^Zombie Killer /, "").sub(/\n/, " ")
|
@@ -88,16 +88,16 @@ class RSpecRenderer < Redcarpet::Render::Base
|
|
88
88
|
nil
|
89
89
|
end
|
90
90
|
|
91
|
-
def block_code(code,
|
91
|
+
def block_code(code, _language)
|
92
92
|
case @next_block_type
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
93
|
+
when :original
|
94
|
+
@original_code = code[0..-2]
|
95
|
+
when :translated
|
96
|
+
@translated_code = code[0..-2]
|
97
|
+
when :unchanged
|
98
|
+
@original_code = @translated_code = code[0..-2]
|
99
|
+
else
|
100
|
+
raise "Invalid next code block type: #{@next_block_type}.\n#{code}"
|
101
101
|
end
|
102
102
|
@next_block_type = :unknown
|
103
103
|
|
@@ -117,11 +117,11 @@ class RSpecRenderer < Redcarpet::Render::Base
|
|
117
117
|
|
118
118
|
def doc_header
|
119
119
|
Code.join([
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
120
|
+
"# Generated from spec/zombie_killer_spec.md -- do not change!",
|
121
|
+
"",
|
122
|
+
"require \"spec_helper\"",
|
123
|
+
""
|
124
|
+
])
|
125
125
|
end
|
126
126
|
|
127
127
|
def doc_footer
|
@@ -142,9 +142,7 @@ class RSpecRenderer < Redcarpet::Render::Base
|
|
142
142
|
|
143
143
|
def current_describe
|
144
144
|
describe = @describe
|
145
|
-
while describe.blocks.last.is_a?(Describe)
|
146
|
-
describe = describe.blocks.last
|
147
|
-
end
|
145
|
+
describe = describe.blocks.last while describe.blocks.last.is_a?(Describe)
|
148
146
|
describe
|
149
147
|
end
|
150
148
|
|
data/spec/rule_spec.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
require "zombie_killer/rule"
|
5
|
+
|
6
|
+
describe Rule do
|
7
|
+
include AST::Sexp # the `s` method
|
8
|
+
extend AST::Sexp
|
9
|
+
|
10
|
+
BUILTINS = s(:const, nil, :Builtins)
|
11
|
+
|
12
|
+
context "a simple const rule" do
|
13
|
+
let(:rule) do
|
14
|
+
Rule.new(
|
15
|
+
from: s(:const, nil, :Round),
|
16
|
+
to: s(:lvar, :square)
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#match" do
|
21
|
+
it "matches what it should" do
|
22
|
+
node = s(:const, nil, :Round)
|
23
|
+
expect(!!rule.match(node)).to eq(true)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "does not match nil" do
|
27
|
+
expect(!!rule.match(nil)).to eq(false)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "does not match a different const" do
|
31
|
+
node = s(:const, nil, :Square)
|
32
|
+
expect(!!rule.match(node)).to eq(false)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "does not match a differently namespaced const" do
|
36
|
+
node = s(:const, s(:const, nil, :Square), :Round)
|
37
|
+
expect(!!rule.match(node)).to eq(false)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "a capturing rule" do
|
43
|
+
let(:rule) do
|
44
|
+
Rule.new(
|
45
|
+
from: s(:send, BUILTINS, :size, Rule::Arg), # Builtins.size(ARG1)
|
46
|
+
to: ->(a) { s(:send, a, :size) } # ARG1.size
|
47
|
+
)
|
48
|
+
end
|
49
|
+
let(:node) { s(:send, BUILTINS, :size, s(:send, nil, :foo)) }
|
50
|
+
|
51
|
+
describe "#match2" do
|
52
|
+
it "returns the captured node" do
|
53
|
+
expect(rule.match2(rule.from, node)).to eq([s(:send, nil, :foo)])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#match" do
|
58
|
+
it "returns the replacement" do
|
59
|
+
expect(rule.match(node)).to eq(s(:send, s(:send, nil, :foo), :size))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/zombie_killer_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Generated from spec/zombie_killer_spec.md -- do not change!
|
2
4
|
|
3
5
|
require "spec_helper"
|
@@ -86,7 +88,7 @@ describe "ZombieKiller:" do
|
|
86
88
|
expect(ZombieKiller.new.kill(original_code)).to eq(translated_code)
|
87
89
|
end
|
88
90
|
|
89
|
-
it "doesn't translate `Ops.add(nice_variable, literal)` when the variable got it's niceness via multiple
|
91
|
+
it "doesn't translate `Ops.add(nice_variable, literal)` when the variable got it's niceness via multiple assignment" do
|
90
92
|
original_code = cleanup(<<-EOT)
|
91
93
|
v1, v2 = "Hello", "World"
|
92
94
|
Ops.add(v1, v2)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zombie-killer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.5'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martin Vidner
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-11-
|
12
|
+
date: 2018-11-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: docopt
|
@@ -127,6 +127,20 @@ dependencies:
|
|
127
127
|
- - "~>"
|
128
128
|
- !ruby/object:Gem::Version
|
129
129
|
version: '3'
|
130
|
+
- !ruby/object:Gem::Dependency
|
131
|
+
name: rubocop
|
132
|
+
requirement: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - '='
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: 0.41.2
|
137
|
+
type: :development
|
138
|
+
prerelease: false
|
139
|
+
version_requirements: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - '='
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: 0.41.2
|
130
144
|
- !ruby/object:Gem::Dependency
|
131
145
|
name: simplecov
|
132
146
|
requirement: !ruby/object:Gem::Requirement
|
@@ -163,13 +177,15 @@ files:
|
|
163
177
|
- lib/zombie_killer/niceness.rb
|
164
178
|
- lib/zombie_killer/node_type_counter.rb
|
165
179
|
- lib/zombie_killer/rewriter.rb
|
180
|
+
- lib/zombie_killer/rule.rb
|
166
181
|
- lib/zombie_killer/variable_scope.rb
|
167
182
|
- lib/zombie_killer/version.rb
|
168
183
|
- spec/rspec_renderer.rb
|
184
|
+
- spec/rule_spec.rb
|
169
185
|
- spec/spec_helper.rb
|
170
186
|
- spec/zombie_killer_spec.md
|
171
187
|
- spec/zombie_killer_spec.rb
|
172
|
-
homepage:
|
188
|
+
homepage: https://github.com/yast/zombie-killer
|
173
189
|
licenses:
|
174
190
|
- MIT
|
175
191
|
metadata: {}
|