analyst 0.0.1 → 0.13.1
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/.gitignore +4 -1
- data/.rspec +2 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/README.md +1 -0
- data/analyst.gemspec +12 -0
- data/lib/analyst/analyzer.rb +162 -0
- data/lib/analyst/cli.rb +42 -0
- data/lib/analyst/entity_parser/association.rb +24 -0
- data/lib/analyst/entity_parser/entities/class.rb +92 -0
- data/lib/analyst/entity_parser/entities/empty.rb +13 -0
- data/lib/analyst/entity_parser/entities/entity.rb +29 -0
- data/lib/analyst/entity_parser/entities/method.rb +16 -0
- data/lib/analyst/entity_parser/entities/module.rb +31 -0
- data/lib/analyst/formatters/base.rb +33 -0
- data/lib/analyst/formatters/csv.rb +43 -0
- data/lib/analyst/formatters/html.rb +87 -0
- data/lib/analyst/formatters/html_index.rb +47 -0
- data/lib/analyst/formatters/templates/index.html.haml +92 -0
- data/lib/analyst/formatters/templates/output.html.haml +114 -0
- data/lib/analyst/formatters/text.rb +56 -0
- data/lib/analyst/fukuzatsu/analyzer.rb +162 -0
- data/lib/analyst/fukuzatsu/cli.rb +42 -0
- data/lib/analyst/fukuzatsu/entity_parser/association.rb +24 -0
- data/lib/analyst/fukuzatsu/entity_parser/entities/class.rb +92 -0
- data/lib/analyst/fukuzatsu/entity_parser/entities/empty.rb +13 -0
- data/lib/analyst/fukuzatsu/entity_parser/entities/entity.rb +29 -0
- data/lib/analyst/fukuzatsu/entity_parser/entities/method.rb +16 -0
- data/lib/analyst/fukuzatsu/entity_parser/entities/module.rb +31 -0
- data/lib/analyst/fukuzatsu/formatters/base.rb +33 -0
- data/lib/analyst/fukuzatsu/formatters/csv.rb +43 -0
- data/lib/analyst/fukuzatsu/formatters/html.rb +87 -0
- data/lib/analyst/fukuzatsu/formatters/html_index.rb +47 -0
- data/lib/analyst/fukuzatsu/formatters/templates/index.html.haml +92 -0
- data/lib/analyst/fukuzatsu/formatters/templates/output.html.haml +114 -0
- data/lib/analyst/fukuzatsu/formatters/text.rb +56 -0
- data/lib/analyst/fukuzatsu/line_of_code.rb +19 -0
- data/lib/analyst/fukuzatsu/parsed_file.rb +85 -0
- data/lib/analyst/fukuzatsu/parsed_method.rb +32 -0
- data/lib/analyst/fukuzatsu/parser_original.rb +76 -0
- data/lib/analyst/fukuzatsu/rethink/parser.rb +346 -0
- data/lib/analyst/fukuzatsu/version.rb +3 -0
- data/lib/analyst/line_of_code.rb +19 -0
- data/lib/analyst/parsed_file.rb +85 -0
- data/lib/analyst/parsed_method.rb +32 -0
- data/lib/analyst/parser.rb +76 -0
- data/lib/analyst/rethink/parser.rb +346 -0
- data/lib/analyst/version.rb +1 -1
- data/lib/analyst.rb +17 -2
- data/spec/analyzer_spec.rb +122 -0
- data/spec/cli_spec.rb +48 -0
- data/spec/fixtures/eg_class.rb +8 -0
- data/spec/fixtures/eg_mod_class.rb +2 -0
- data/spec/fixtures/eg_mod_class_2.rb +5 -0
- data/spec/fixtures/eg_module.rb +2 -0
- data/spec/fixtures/module_with_class.rb +9 -0
- data/spec/fixtures/multiple_methods.rb +7 -0
- data/spec/fixtures/nested_methods.rb +8 -0
- data/spec/fixtures/program_1.rb +19 -0
- data/spec/fixtures/program_2.rb +25 -0
- data/spec/fixtures/program_3.rb +66 -0
- data/spec/fixtures/program_4.rb +1 -0
- data/spec/fixtures/single_class.rb +9 -0
- data/spec/fixtures/single_method.rb +3 -0
- data/spec/formatters/csv_spec.rb +37 -0
- data/spec/formatters/html_index_spec.rb +36 -0
- data/spec/formatters/html_spec.rb +48 -0
- data/spec/formatters/text_spec.rb +39 -0
- data/spec/parsed_file_spec.rb +67 -0
- data/spec/parsed_method_spec.rb +34 -0
- data/spec/spec_helper.rb +7 -0
- metadata +229 -2
@@ -0,0 +1,346 @@
|
|
1
|
+
require 'pry'
|
2
|
+
|
3
|
+
module Z
|
4
|
+
|
5
|
+
class Parser
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_reader :corpus
|
9
|
+
|
10
|
+
def_delegators :@corpus, :classes, :associations
|
11
|
+
|
12
|
+
def initialize(path)
|
13
|
+
@corpus = Corpus.new(path)
|
14
|
+
end
|
15
|
+
|
16
|
+
def class_graph
|
17
|
+
{
|
18
|
+
nodes: corpus.classes,
|
19
|
+
edges: corpus.associations
|
20
|
+
#nodes: [1,2,3],
|
21
|
+
#edges: [{source: 1, target: 2, strength: 1}, {source: 2, target: 1, strength: 4}]
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
class Corpus
|
28
|
+
|
29
|
+
attr_reader :path, :classes, :associations
|
30
|
+
|
31
|
+
def initialize(path)
|
32
|
+
@path = path
|
33
|
+
parse!
|
34
|
+
end
|
35
|
+
|
36
|
+
def files
|
37
|
+
@files ||= file_paths.map {|path| File.new(path)}
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse!
|
41
|
+
@classes = extract_classes
|
42
|
+
@associations = link_associations
|
43
|
+
#@associations = combine_identical_associations
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def extract_classes
|
49
|
+
classes = []
|
50
|
+
files.each {|f| f.classes.each {|c| merge_class(classes, c) } }
|
51
|
+
classes
|
52
|
+
end
|
53
|
+
|
54
|
+
def link_associations
|
55
|
+
all = classes.map(&:associations).flatten
|
56
|
+
all.each do |assoc|
|
57
|
+
target = classes.detect {|c| c.full_name == assoc.target_class }
|
58
|
+
if target
|
59
|
+
assoc.target = target
|
60
|
+
else
|
61
|
+
puts "WARNING: Couldn't find target: #{assoc.target_class}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def file_paths
|
67
|
+
if ::File.directory?(path)
|
68
|
+
Dir.glob(::File.join(path, "**", "*.rb"))
|
69
|
+
else
|
70
|
+
[path]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def merge_class(classes, klass)
|
75
|
+
existing = classes.find {|c| c.full_name == klass.full_name}
|
76
|
+
if existing
|
77
|
+
existing.merge(klass)
|
78
|
+
else
|
79
|
+
classes << klass
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
class File
|
87
|
+
|
88
|
+
attr_reader :path
|
89
|
+
|
90
|
+
def initialize(path)
|
91
|
+
@path = path
|
92
|
+
end
|
93
|
+
|
94
|
+
def contents
|
95
|
+
@contents ||= ::File.open(path, 'r').read
|
96
|
+
end
|
97
|
+
|
98
|
+
def ast
|
99
|
+
@ast ||= ::Parser::CurrentRuby.parse(contents)
|
100
|
+
end
|
101
|
+
|
102
|
+
def classes
|
103
|
+
return @classes if @classes
|
104
|
+
@classes = []
|
105
|
+
parse!
|
106
|
+
@classes
|
107
|
+
end
|
108
|
+
|
109
|
+
def parse!
|
110
|
+
parse_ast
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
# to log node types that haven't been dealt with
|
117
|
+
def unhandled_node_types
|
118
|
+
@unhandled_node_types ||= Set.new
|
119
|
+
end
|
120
|
+
|
121
|
+
# contains Module, Class, Method... eventually Block and other stuff maybe
|
122
|
+
def context_stack
|
123
|
+
@context_stack ||= [Entities::Empty.new]
|
124
|
+
end
|
125
|
+
|
126
|
+
def node_parsers
|
127
|
+
@node_parsers ||= Hash.new(:default_node_parser).merge!(
|
128
|
+
:class => :class_node_parser,
|
129
|
+
:module => :module_node_parser,
|
130
|
+
:def => :method_node_parser,
|
131
|
+
:send => :send_node_parser
|
132
|
+
# TODO: make a method parser, which pushes the the context_stack so that things inside method bodies
|
133
|
+
# are treated differently than those inside class or module bodies. same with Block (right?)
|
134
|
+
)
|
135
|
+
end
|
136
|
+
|
137
|
+
# search the whole tree for classes
|
138
|
+
def parse_ast(node=ast)
|
139
|
+
# files can start with (module), (class), or (begin)
|
140
|
+
# EDIT: or (send), or (def), or.... just about anything
|
141
|
+
return unless node.respond_to? :type
|
142
|
+
send(node_parsers[node.type], node)
|
143
|
+
end
|
144
|
+
|
145
|
+
def default_node_parser(node)
|
146
|
+
unhandled_node_types.add(node.type)
|
147
|
+
return unless node.respond_to? :children
|
148
|
+
node.children.each { |cnode| parse_ast(cnode) }
|
149
|
+
end
|
150
|
+
|
151
|
+
def class_node_parser(node)
|
152
|
+
name_node, super_node, content_node = node.children
|
153
|
+
klass = Entities::Class.new(context_stack.last, node)
|
154
|
+
@classes << klass
|
155
|
+
context_stack.push klass
|
156
|
+
parse_ast(content_node)
|
157
|
+
context_stack.pop
|
158
|
+
end
|
159
|
+
|
160
|
+
def module_node_parser(node)
|
161
|
+
name_node, content_node = node.children
|
162
|
+
mod = Entities::Module.new(context_stack.last, node)
|
163
|
+
context_stack.push mod
|
164
|
+
parse_ast(content_node)
|
165
|
+
context_stack.pop
|
166
|
+
end
|
167
|
+
|
168
|
+
def method_node_parser(node)
|
169
|
+
name, args_node, content_node = node.children
|
170
|
+
method = Entities::Method.new(context_stack.last, node)
|
171
|
+
context_stack.push method
|
172
|
+
parse_ast(content_node)
|
173
|
+
context_stack.pop
|
174
|
+
end
|
175
|
+
|
176
|
+
def send_node_parser(node)
|
177
|
+
target_node, method_name, *args = node.children
|
178
|
+
# if method_name is an association, then analyze the args to see what class it's associated with!
|
179
|
+
# and "it", btw, is the context_stack.last... so ya, also make sure this analysis is only done when we're in a
|
180
|
+
# class, NOT when we're in a method, NOT when we're in a defs, NOT when we're in an (sclass) (comes from
|
181
|
+
# class << self) -- got that?? those nodes should push something like 'Unsupported' onto the stack, or something
|
182
|
+
# just to keep track of where we are at any given point, and make sure we only care about sends that
|
183
|
+
# are truly at the right scope.
|
184
|
+
context_stack.last.handle_send_node(node)
|
185
|
+
# basically, if it's a class, it'll see if this is an association. if so, it'll store it by name. later on, we go
|
186
|
+
# thru and connect the pointers.
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
module Entities
|
192
|
+
|
193
|
+
class Entity
|
194
|
+
attr_reader :parent
|
195
|
+
|
196
|
+
def handle_send_node(node)
|
197
|
+
# abstract method. btw, this feels wrong -- send should be an entity too. but for now, whatevs.
|
198
|
+
end
|
199
|
+
|
200
|
+
def full_name
|
201
|
+
throw "this is abstract method, fool"
|
202
|
+
end
|
203
|
+
|
204
|
+
def inspect
|
205
|
+
"\#<#{self.class}:#{object_id} full_name=#{full_name}>"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
class Empty < Entity
|
211
|
+
def full_name
|
212
|
+
''
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
class Method < Entity
|
218
|
+
attr_reader :ast
|
219
|
+
|
220
|
+
def initialize(parent, ast)
|
221
|
+
@parent = parent
|
222
|
+
@ast = ast
|
223
|
+
end
|
224
|
+
|
225
|
+
def name
|
226
|
+
ast.children.first.to_s
|
227
|
+
end
|
228
|
+
|
229
|
+
def full_name
|
230
|
+
parent.full_name + '#' + name
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
class Module < Entity
|
236
|
+
attr_reader :ast
|
237
|
+
|
238
|
+
def initialize(parent, ast)
|
239
|
+
@parent = parent
|
240
|
+
@ast = ast
|
241
|
+
end
|
242
|
+
|
243
|
+
def name
|
244
|
+
const_node_array(ast.children.first).join('::')
|
245
|
+
end
|
246
|
+
|
247
|
+
def full_name
|
248
|
+
parent.full_name.empty? ? name : parent.full_name + '::' + name
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
# takes a (const) node and returns an array specifying the fully-qualified
|
254
|
+
# constant name that it represents. ya know, so CoolModule::SubMod::SweetClass
|
255
|
+
# would be parsed to:
|
256
|
+
# (const
|
257
|
+
# (const
|
258
|
+
# (const nil :CoolModule) :SubMod) :SweetClass)
|
259
|
+
# and passing that node here would return [:CoolModule, :SubMod, :SweetClass]
|
260
|
+
def const_node_array(node)
|
261
|
+
return [] if node.nil?
|
262
|
+
raise "expected (const) node or nil, got (#{node.type})" unless node.type == :const
|
263
|
+
const_node_array(node.children.first) << node.children[1]
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
|
268
|
+
class Class < Module
|
269
|
+
|
270
|
+
# pretend, for now, that we always have an AR. i.e., always look for associations.
|
271
|
+
ASSOCIATIONS = [:belongs_to, :has_one, :has_many, :has_and_belongs_to_many]
|
272
|
+
|
273
|
+
def handle_send_node(node)
|
274
|
+
# FIXME: this doesn't feel right, cuz (send) should probably be an entity too, especially
|
275
|
+
# since you can have nested sends... but i'm doing it this way for now.
|
276
|
+
target, method_name, *args = node.children
|
277
|
+
if ASSOCIATIONS.include? method_name
|
278
|
+
add_association(method_name, args)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def associations
|
283
|
+
@associations ||= []
|
284
|
+
end
|
285
|
+
|
286
|
+
def merge(other_class)
|
287
|
+
other_class.associations.each do |other_assoc|
|
288
|
+
duplicate = associations.detect do |assoc|
|
289
|
+
assoc.type == other_assoc.type && assoc.target_class == other_assoc.target_class
|
290
|
+
end
|
291
|
+
unless duplicate
|
292
|
+
associations << Association.new(type: other_assoc.type, source: self, target_class: other_assoc.target_class)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
private
|
298
|
+
|
299
|
+
def add_association(method_name, args)
|
300
|
+
if args.size > 1
|
301
|
+
# args.last is a hash that might contain a class_name or a through
|
302
|
+
target_class = hash_val_str(args.last, :class_name)
|
303
|
+
end
|
304
|
+
target_class ||= begin
|
305
|
+
symbol_node = args.first
|
306
|
+
symbol_name = symbol_node.children.first
|
307
|
+
table_name = ::ActiveSupport::Inflector.pluralize(symbol_name)
|
308
|
+
::ActiveSupport::Inflector.classify(table_name)
|
309
|
+
end
|
310
|
+
assoc = Association.new(type: method_name, source: self, target_class: target_class)
|
311
|
+
associations << assoc
|
312
|
+
end
|
313
|
+
|
314
|
+
# give it a (hash) node and a key (a symbol) and it'll look for that key in the hash and
|
315
|
+
# return the associated value as long as it's a string. if key isn't found, returns nil.
|
316
|
+
# if key is found but val isn't (str), throw exception. yep, this is pretty bespoke.
|
317
|
+
def hash_val_str(node, key)
|
318
|
+
return unless node.type == :hash
|
319
|
+
pair = node.children.detect do |pair_node|
|
320
|
+
key_sym_node = pair_node.children.first
|
321
|
+
key == key_sym_node.children.first
|
322
|
+
end
|
323
|
+
if pair
|
324
|
+
val_node = pair.children.last
|
325
|
+
throw "Bad type. Expected (str), got (#{val_node.type})" unless val_node.type == :str
|
326
|
+
val_node.children.first
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
|
333
|
+
|
334
|
+
class Association
|
335
|
+
attr_reader :type, :source, :target_class
|
336
|
+
attr_accessor :target
|
337
|
+
|
338
|
+
def initialize(type:, source:, target_class:)
|
339
|
+
@type = type
|
340
|
+
@source = source
|
341
|
+
@target_class = target_class
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
|
data/lib/analyst/version.rb
CHANGED
data/lib/analyst.rb
CHANGED
@@ -1,5 +1,20 @@
|
|
1
|
-
require
|
1
|
+
require 'ephemeral'
|
2
|
+
require 'poro_plus'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'haml'
|
5
|
+
|
6
|
+
require_relative "analyst/analyzer"
|
7
|
+
require_relative "analyst/cli"
|
8
|
+
require_relative "analyst/formatters/base"
|
9
|
+
require_relative "analyst/formatters/csv"
|
10
|
+
require_relative "analyst/formatters/html"
|
11
|
+
require_relative "analyst/formatters/html_index"
|
12
|
+
require_relative "analyst/formatters/text"
|
13
|
+
require_relative "analyst/line_of_code"
|
14
|
+
require_relative "analyst/parsed_file"
|
15
|
+
require_relative "analyst/parsed_method"
|
16
|
+
require_relative "analyst/parser"
|
17
|
+
require_relative "analyst/version"
|
2
18
|
|
3
19
|
module Analyst
|
4
|
-
# Your code goes here...
|
5
20
|
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Analyzer do
|
4
|
+
|
5
|
+
let(:content_1) { File.read("spec/fixtures/program_1.rb") }
|
6
|
+
let(:content_2) { File.read("spec/fixtures/program_2.rb") }
|
7
|
+
let(:content_3) { File.read("spec/fixtures/eg_class.rb") }
|
8
|
+
let(:content_4) { File.read("spec/fixtures/eg_mod_class.rb") }
|
9
|
+
let(:content_5) { File.read("spec/fixtures/eg_module.rb") }
|
10
|
+
let(:content_6) { File.read("spec/fixtures/eg_mod_class_2.rb") }
|
11
|
+
|
12
|
+
context "#extract_class_name" do
|
13
|
+
|
14
|
+
context "from Class Foo" do
|
15
|
+
it "returns Foo" do
|
16
|
+
expect(Analyzer.new(content_3).extract_class_name).to eq("Foo")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "from Module::Class Foo" do
|
21
|
+
it "returns Foo::Bar" do
|
22
|
+
expect(Analyzer.new(content_4).extract_class_name).to eq("Foo::Bar")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "from Module; Class" do
|
27
|
+
it "returns Extracted::Thing" do
|
28
|
+
expect(Analyzer.new(content_6).extract_class_name).to eq("Extracted::Thing")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "from Module" do
|
33
|
+
it "returns Something" do
|
34
|
+
expect(Analyzer.new(content_5).extract_class_name).to eq("Something")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#complexity" do
|
40
|
+
context "program_1" do
|
41
|
+
|
42
|
+
let(:analyzer) { Analyzer.new(content_1) }
|
43
|
+
|
44
|
+
it "matches the manual calculation of complexity of 4" do
|
45
|
+
expect(analyzer.complexity).to eq(4)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
context "program_2" do
|
51
|
+
|
52
|
+
let(:analyzer) { Analyzer.new(content_2) }
|
53
|
+
|
54
|
+
it "matches the manual calculation of complexity of 5" do
|
55
|
+
expect(analyzer.complexity).to eq(5)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "extract_class_name" do
|
62
|
+
context "from a file with a class in it" do
|
63
|
+
let(:analyzer) { Analyzer.new(File.read("spec/fixtures/single_class.rb")) }
|
64
|
+
it "should return the name of the class" do
|
65
|
+
expect(analyzer.extract_class_name).to eq "Gown"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
context "from a file with a class inside a module" do
|
69
|
+
let(:analyzer) { Analyzer.new(File.read("spec/fixtures/module_with_class.rb")) }
|
70
|
+
it "should return the name of the class" do
|
71
|
+
expect(analyzer.extract_class_name).to eq "Symbolics::Insects::Bee"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
context "from a file with no class in it" do
|
75
|
+
let(:analyzer) { Analyzer.new(File.read("spec/fixtures/single_method.rb")) }
|
76
|
+
it "should return 'Unknown'" do
|
77
|
+
expect(analyzer.extract_class_name).to eq "Unknown"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "extract_methods" do
|
84
|
+
# Note: should implicitly trust private method #methods_from
|
85
|
+
context "from a file with a single method" do
|
86
|
+
let(:analyzer) { Analyzer.new(File.read("spec/fixtures/single_method.rb")) }
|
87
|
+
it "should return a single method" do
|
88
|
+
expect(analyzer.extract_methods.count).to eq 1
|
89
|
+
end
|
90
|
+
it "should extract the method name" do
|
91
|
+
expect(analyzer.extract_methods[0].name).to eq "#read_poem"
|
92
|
+
end
|
93
|
+
it "should extract the method content" do
|
94
|
+
expect(analyzer.extract_methods[0].content).to eq 'def read_poem
|
95
|
+
return "I meant to find her when I came/Death had the same design"
|
96
|
+
end'
|
97
|
+
end
|
98
|
+
it "should set type to :instance" do
|
99
|
+
expect(analyzer.extract_methods[0].type).to eq :instance
|
100
|
+
end
|
101
|
+
end
|
102
|
+
context "from a file with multiple methods" do
|
103
|
+
let(:analyzer) { Analyzer.new(File.read("spec/fixtures/multiple_methods.rb")) }
|
104
|
+
it "should return multiple methods" do
|
105
|
+
expect(analyzer.extract_methods.map { |m| m.name }).to eq ["#bake_treats", "#lower_from_window"]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
context "from a file with nested methods" do
|
109
|
+
let(:analyzer) { Analyzer.new(File.read("spec/fixtures/nested_methods.rb")) }
|
110
|
+
it "should return the root method, and its child" do
|
111
|
+
expect(analyzer.extract_methods.map { |m| m.name }).to eq ["#grow_flowers", "#water_earth"]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
context "from a file with a class" do
|
115
|
+
let(:analyzer) { Analyzer.new(File.read("spec/fixtures/single_class.rb")) }
|
116
|
+
it "should return the class and its methods" do
|
117
|
+
expect(analyzer.extract_methods.map { |m| m.name }).to eq ["#initialize", "#color"]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
data/spec/cli_spec.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Analyst::CLI" do
|
4
|
+
|
5
|
+
let(:cli) { Analyst::CLI.new([
|
6
|
+
path: "foo/bar.rb",
|
7
|
+
format: 'text'
|
8
|
+
]
|
9
|
+
)}
|
10
|
+
|
11
|
+
let(:summary_1) {
|
12
|
+
{
|
13
|
+
results_file: "doc/analyst/file_1.rb.htm",
|
14
|
+
path_to_file: "file_1.rb",
|
15
|
+
class_name: "File_1",
|
16
|
+
complexity: 13
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
let(:summary_2) {
|
21
|
+
{
|
22
|
+
results_file: "doc/analyst/file_2.rb.htm",
|
23
|
+
path_to_file: "file_2.rb",
|
24
|
+
class_name: "File_2",
|
25
|
+
complexity: 11
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
before do
|
30
|
+
allow(cli).to receive(:summaries) { [summary_1, summary_2] }
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#formatter" do
|
34
|
+
it "returns a csv formatter" do
|
35
|
+
allow(cli).to receive(:options) { {'format' => 'csv'} }
|
36
|
+
expect(cli.send(:formatter)).to eq Formatters::Csv
|
37
|
+
end
|
38
|
+
it "returns an html formatter" do
|
39
|
+
allow(cli).to receive(:options) { {'format' => 'html'} }
|
40
|
+
expect(cli.send(:formatter)).to eq Formatters::Html
|
41
|
+
end
|
42
|
+
it "returns a text formatter" do
|
43
|
+
allow(cli).to receive(:options) { {'format' => 'text'} }
|
44
|
+
expect(cli.send(:formatter)).to eq Formatters::Text
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
def toppings
|
2
|
+
|
3
|
+
if rand(10) < 5
|
4
|
+
return "meatball"
|
5
|
+
end
|
6
|
+
|
7
|
+
if 1 < 5
|
8
|
+
return 'tomato'
|
9
|
+
else
|
10
|
+
if 4 ==5
|
11
|
+
if 2 > 0
|
12
|
+
return "parmesan"
|
13
|
+
else
|
14
|
+
return "romano"
|
15
|
+
end
|
16
|
+
else
|
17
|
+
if 2 > 0
|
18
|
+
return "alfredo"
|
19
|
+
else
|
20
|
+
return "gravy"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|