zombie-killer 0.4 → 0.5
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/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: {}
|