analyst 0.13.1 → 0.14.0
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/analyst.gemspec +4 -3
- data/lib/analyst/{entity_parser/association.rb → association.rb} +0 -0
- data/lib/analyst/entities/begin.rb +13 -0
- data/lib/analyst/entities/class.rb +97 -0
- data/lib/analyst/entities/empty.rb +10 -0
- data/lib/analyst/entities/entity.rb +67 -0
- data/lib/analyst/entities/method.rb +41 -0
- data/lib/analyst/entities/method_call.rb +28 -0
- data/lib/analyst/entities/module.rb +33 -0
- data/lib/analyst/entities/root.rb +22 -0
- data/lib/analyst/entities/singleton_class.rb +21 -0
- data/lib/analyst/parser.rb +30 -55
- data/lib/analyst/version.rb +1 -1
- data/lib/analyst.rb +40 -9
- data/spec/class_spec.rb +30 -0
- data/spec/fixtures/music.rb +70 -0
- data/spec/method_spec.rb +9 -0
- data/spec/parser_spec.rb +29 -0
- data/spec/spec_helper.rb +1 -0
- metadata +38 -87
- data/lib/analyst/cli.rb +0 -42
- data/lib/analyst/entity_parser/entities/class.rb +0 -92
- data/lib/analyst/entity_parser/entities/empty.rb +0 -13
- data/lib/analyst/entity_parser/entities/entity.rb +0 -29
- data/lib/analyst/entity_parser/entities/method.rb +0 -16
- data/lib/analyst/entity_parser/entities/module.rb +0 -31
- data/lib/analyst/formatters/base.rb +0 -33
- data/lib/analyst/formatters/csv.rb +0 -43
- data/lib/analyst/formatters/html.rb +0 -87
- data/lib/analyst/formatters/html_index.rb +0 -47
- data/lib/analyst/formatters/templates/index.html.haml +0 -92
- data/lib/analyst/formatters/templates/output.html.haml +0 -114
- data/lib/analyst/formatters/text.rb +0 -56
- data/lib/analyst/fukuzatsu/analyzer.rb +0 -162
- data/lib/analyst/fukuzatsu/cli.rb +0 -42
- data/lib/analyst/fukuzatsu/entity_parser/association.rb +0 -24
- data/lib/analyst/fukuzatsu/entity_parser/entities/class.rb +0 -92
- data/lib/analyst/fukuzatsu/entity_parser/entities/empty.rb +0 -13
- data/lib/analyst/fukuzatsu/entity_parser/entities/entity.rb +0 -29
- data/lib/analyst/fukuzatsu/entity_parser/entities/method.rb +0 -16
- data/lib/analyst/fukuzatsu/entity_parser/entities/module.rb +0 -31
- data/lib/analyst/fukuzatsu/formatters/base.rb +0 -33
- data/lib/analyst/fukuzatsu/formatters/csv.rb +0 -43
- data/lib/analyst/fukuzatsu/formatters/html.rb +0 -87
- data/lib/analyst/fukuzatsu/formatters/html_index.rb +0 -47
- data/lib/analyst/fukuzatsu/formatters/templates/index.html.haml +0 -92
- data/lib/analyst/fukuzatsu/formatters/templates/output.html.haml +0 -114
- data/lib/analyst/fukuzatsu/formatters/text.rb +0 -56
- data/lib/analyst/fukuzatsu/line_of_code.rb +0 -19
- data/lib/analyst/fukuzatsu/parsed_file.rb +0 -85
- data/lib/analyst/fukuzatsu/parsed_method.rb +0 -32
- data/lib/analyst/fukuzatsu/parser_original.rb +0 -76
- data/lib/analyst/fukuzatsu/rethink/parser.rb +0 -346
- data/lib/analyst/fukuzatsu/version.rb +0 -3
- data/lib/analyst/line_of_code.rb +0 -19
- data/lib/analyst/parsed_file.rb +0 -85
- data/lib/analyst/parsed_method.rb +0 -32
- data/lib/analyst/rethink/parser.rb +0 -346
- data/spec/analyzer_spec.rb +0 -122
- data/spec/cli_spec.rb +0 -48
- data/spec/fixtures/eg_class.rb +0 -8
- data/spec/fixtures/eg_mod_class.rb +0 -2
- data/spec/fixtures/eg_mod_class_2.rb +0 -5
- data/spec/fixtures/eg_module.rb +0 -2
- data/spec/fixtures/module_with_class.rb +0 -9
- data/spec/fixtures/multiple_methods.rb +0 -7
- data/spec/fixtures/nested_methods.rb +0 -8
- data/spec/fixtures/program_1.rb +0 -19
- data/spec/fixtures/program_2.rb +0 -25
- data/spec/fixtures/program_3.rb +0 -66
- data/spec/fixtures/program_4.rb +0 -1
- data/spec/fixtures/single_class.rb +0 -9
- data/spec/fixtures/single_method.rb +0 -3
- data/spec/formatters/csv_spec.rb +0 -37
- data/spec/formatters/html_index_spec.rb +0 -36
- data/spec/formatters/html_spec.rb +0 -48
- data/spec/formatters/text_spec.rb +0 -39
- data/spec/parsed_file_spec.rb +0 -67
- data/spec/parsed_method_spec.rb +0 -34
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b3e243d014bb8326cde40ec9a77530dc26cd2d77
|
|
4
|
+
data.tar.gz: e70309a0752dacbccddd636e99e4406691a61d44
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fdb9ed57212aca0e6e6cdc243519989e34535cd43342f8c5773751122fb6795b73b2d1ce6282349ae4af8777ad0214709748fda1f05a274cd2fb50263d368c5e
|
|
7
|
+
data.tar.gz: 9e0456a47ff03f707d1dfa30ae61bfaf17501ffd5e24786a6c4c448fb478d2ccb8326e4aae6eaef759e15b9dbba54e8c7e6d0d2dfb5f7a80d47b2f243a9f5166
|
data/analyst.gemspec
CHANGED
|
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.version = Analyst::VERSION
|
|
9
9
|
spec.authors = ["Coraline Ada Ehmke", "Mike Ziwisky"]
|
|
10
10
|
spec.email = ["coraline@idolhands.com", "mikezx@gmail.com"]
|
|
11
|
-
spec.summary = %q{
|
|
12
|
-
spec.description = %q{
|
|
11
|
+
spec.summary = %q{A nice API for interacting with parsed Ruby source code.}
|
|
12
|
+
spec.description = %q{A nice API for interacting with parsed Ruby source code.}
|
|
13
13
|
spec.homepage = ""
|
|
14
14
|
spec.license = "MIT"
|
|
15
15
|
|
|
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
19
|
spec.require_paths = ["lib"]
|
|
20
20
|
|
|
21
|
-
spec.add_dependency "ephemeral"
|
|
21
|
+
spec.add_dependency "ephemeral", ">= 2.4.0"
|
|
22
22
|
spec.add_dependency "poro_plus"
|
|
23
23
|
spec.add_dependency "haml"
|
|
24
24
|
spec.add_dependency "parser"
|
|
@@ -31,5 +31,6 @@ Gem::Specification.new do |spec|
|
|
|
31
31
|
spec.add_development_dependency "rake"
|
|
32
32
|
spec.add_development_dependency "rspec"
|
|
33
33
|
spec.add_development_dependency "simplecov"
|
|
34
|
+
spec.add_development_dependency "pry"
|
|
34
35
|
|
|
35
36
|
end
|
|
File without changes
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#TODO add == to association
|
|
2
|
+
# TODO look thru the singleton_methods for ones on (self),
|
|
3
|
+
# and also look for the ones from 'class << self' constructs, which will be
|
|
4
|
+
# found in (sclass) nodes (which will be some sort of Entity)
|
|
5
|
+
|
|
6
|
+
module Analyst
|
|
7
|
+
|
|
8
|
+
module Entities
|
|
9
|
+
class Class < Analyst::Entities::Module
|
|
10
|
+
|
|
11
|
+
alias :macros :method_calls
|
|
12
|
+
|
|
13
|
+
def imethods
|
|
14
|
+
@imethods ||= contents.select { |entity| entity.is_a? Analyst::Entities::InstanceMethod }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def cmethods
|
|
18
|
+
some_methods = smethods.select { |method| method.target.type == :self }
|
|
19
|
+
other_methods = singleton_class_blocks { |block| block.target.type == :self }.map(&:smethods).flatten
|
|
20
|
+
some_methods + other_methods
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def singleton_class_blocks
|
|
24
|
+
contents.select { |entity| entity.is_a? Analyst::Entities::SingletonClass }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def smethods
|
|
30
|
+
@smethods ||= contents.select do |entity|
|
|
31
|
+
entity.is_a? Analyst::Entities::SingletonMethod
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# ASSOCIATIONS = [:belongs_to, :has_one, :has_many, :has_and_belongs_to_many]
|
|
36
|
+
|
|
37
|
+
# attr_reader :associations
|
|
38
|
+
|
|
39
|
+
# def initialize(node, parent)
|
|
40
|
+
# @associations = []
|
|
41
|
+
# super
|
|
42
|
+
# end
|
|
43
|
+
|
|
44
|
+
# def handle_send(node)
|
|
45
|
+
# target, method_name, *args = node.children
|
|
46
|
+
# if ASSOCIATIONS.include?(method_name)
|
|
47
|
+
# add_association(method_name, args)
|
|
48
|
+
# end
|
|
49
|
+
# end
|
|
50
|
+
|
|
51
|
+
# # When a class is reopened, merge its associations
|
|
52
|
+
# def merge_associations_from(klass)
|
|
53
|
+
# klass.associations.each do |association|
|
|
54
|
+
# associations << Association.new(
|
|
55
|
+
# type: association.type,
|
|
56
|
+
# source: self,
|
|
57
|
+
# target_class: association.target_class
|
|
58
|
+
# )
|
|
59
|
+
# end
|
|
60
|
+
# associations.uniq!
|
|
61
|
+
# end
|
|
62
|
+
|
|
63
|
+
# private
|
|
64
|
+
|
|
65
|
+
# def add_association(method_name, args)
|
|
66
|
+
# target_class = value_from_hash_node(args.last, :class_name)
|
|
67
|
+
# target_class ||= begin
|
|
68
|
+
# symbol_node = args.first
|
|
69
|
+
# symbol_name = symbol_node.children.first
|
|
70
|
+
# symbol_name.pluralize.classify
|
|
71
|
+
# end
|
|
72
|
+
# association = Association.new(type: method_name, source: self, target_class: target_class)
|
|
73
|
+
# associations << association
|
|
74
|
+
# end
|
|
75
|
+
|
|
76
|
+
# private
|
|
77
|
+
|
|
78
|
+
# # Fetches value from hash node iff key is symbol and value is str
|
|
79
|
+
# # Raises an exception if value is not str
|
|
80
|
+
# # Returns nil if key is not found
|
|
81
|
+
# def value_from_hash_node(node, key)
|
|
82
|
+
# return unless node.type == :hash
|
|
83
|
+
# pair = node.children.detect do |pair_node|
|
|
84
|
+
# key_symbol_node = pair_node.children.first
|
|
85
|
+
# key == key_symbol_node.children.first
|
|
86
|
+
# end
|
|
87
|
+
# if pair
|
|
88
|
+
# value_node = pair.children.last
|
|
89
|
+
# throw "Bad type. Expected (str), got (#{value_node.type})" unless value_node.type == :str
|
|
90
|
+
# value_node.children.first
|
|
91
|
+
# end
|
|
92
|
+
# end
|
|
93
|
+
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# An entity is a named node of a given type which may have additional properties
|
|
2
|
+
module Analyst
|
|
3
|
+
module Entities
|
|
4
|
+
class Entity
|
|
5
|
+
|
|
6
|
+
attr_reader :parent
|
|
7
|
+
|
|
8
|
+
def initialize(ast, parent)
|
|
9
|
+
@parent = parent
|
|
10
|
+
@ast = ast
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def handle_send_node(node)
|
|
14
|
+
# raise "Subclass must implement handle_send_node"
|
|
15
|
+
# abstract method. btw, this feels wrong -- send should be an entity too. but for now, whatevs.
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# TODO: should every Entity have these accessors? maybe they're mixins... but would that provide any benefit?
|
|
19
|
+
def classes
|
|
20
|
+
@classes ||= begin
|
|
21
|
+
nested_classes = top_level_classes.map(&:classes).flatten
|
|
22
|
+
namespaced_classes = top_level_modules.map(&:classes).flatten
|
|
23
|
+
top_level_classes + nested_classes + namespaced_classes
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def top_level_modules
|
|
28
|
+
@top_level_modules ||= contents_of_type(Entities::Module)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def top_level_classes
|
|
32
|
+
@top_level_classes ||= contents_of_type(Entities::Class)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def method_calls
|
|
36
|
+
@method_calls ||= contents_of_type(Entities::MethodCall)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def full_name
|
|
40
|
+
throw "Subclass #{self.class.name} must implement #full_name"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def inspect
|
|
44
|
+
"\#<#{self.class}:#{object_id} full_name=#{full_name}>"
|
|
45
|
+
rescue
|
|
46
|
+
"\#<#{self.class}:#{object_id}>"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
attr_reader :ast
|
|
52
|
+
|
|
53
|
+
def contents_of_type(klass)
|
|
54
|
+
contents.select { |entity| entity.is_a? klass }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def contents
|
|
58
|
+
@contents ||= Array(Analyst::Parser.process_node(content_node, self))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def content_node
|
|
62
|
+
ast.children.last
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Analyst
|
|
2
|
+
module Entities
|
|
3
|
+
|
|
4
|
+
class InstanceMethod < Entity
|
|
5
|
+
def name
|
|
6
|
+
ast.children.first.to_s
|
|
7
|
+
end
|
|
8
|
+
def full_name
|
|
9
|
+
parent.full_name + '#' + name
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# TODO HERE
|
|
14
|
+
class ClassMethod < Entity
|
|
15
|
+
def name
|
|
16
|
+
ast.children.first.to_s
|
|
17
|
+
end
|
|
18
|
+
def full_name
|
|
19
|
+
parent.full_name + '::' + name
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class SingletonMethod < Entity
|
|
24
|
+
|
|
25
|
+
# NOTE: not a public API -- used by Entities::Class
|
|
26
|
+
def target
|
|
27
|
+
target, name, params, content = ast.children
|
|
28
|
+
target
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def name
|
|
32
|
+
ast.children[1].to_s
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def full_name
|
|
36
|
+
parent.full_name + '::' + name
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Analyst
|
|
2
|
+
module Entities
|
|
3
|
+
class MethodCall < Entity
|
|
4
|
+
|
|
5
|
+
def name
|
|
6
|
+
name_node.to_s
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def full_name
|
|
10
|
+
name
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# TODO: figure out how to resolve this to an Entity. we never want
|
|
14
|
+
# to expose the AST to the outside.
|
|
15
|
+
def target_node
|
|
16
|
+
ast.children.first
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def name_node
|
|
22
|
+
ast.children[1]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Analyst
|
|
2
|
+
module Entities
|
|
3
|
+
class Module < Entity
|
|
4
|
+
|
|
5
|
+
def name
|
|
6
|
+
const_node_array(name_node).join('::')
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def full_name
|
|
10
|
+
parent.full_name.empty? ? name : parent.full_name + '::' + name
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def name_node
|
|
16
|
+
ast.children.first
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# takes a (const) node and returns an array specifying the fully-qualified
|
|
20
|
+
# constant name that it represents. ya know, so CoolModule::SubMod::SweetClass
|
|
21
|
+
# would be parsed to:
|
|
22
|
+
# (const
|
|
23
|
+
# (const
|
|
24
|
+
# (const nil :CoolModule) :SubMod) :SweetClass)
|
|
25
|
+
# and passing that node here would return [:CoolModule, :SubMod, :SweetClass]
|
|
26
|
+
def const_node_array(node)
|
|
27
|
+
return [] if node.nil?
|
|
28
|
+
raise "expected (const) node or nil, got (#{node.type})" unless node.type == :const
|
|
29
|
+
const_node_array(node.children.first) << node.children[1]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Analyst
|
|
2
|
+
|
|
3
|
+
module Entities
|
|
4
|
+
class Root < Entity
|
|
5
|
+
|
|
6
|
+
def full_name
|
|
7
|
+
""
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def contents
|
|
13
|
+
@contents ||= begin
|
|
14
|
+
child_nodes = ast.children
|
|
15
|
+
child_nodes.map { |child| Analyst::Parser.process_node(child, self) }.flatten
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Analyst
|
|
2
|
+
|
|
3
|
+
module Entities
|
|
4
|
+
class SingletonClass < Entity
|
|
5
|
+
|
|
6
|
+
def full_name
|
|
7
|
+
parent.full_name + "!SINGLETON"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def name
|
|
11
|
+
parent.name + "!SINGLETON"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def smethods
|
|
15
|
+
@smethods ||= contents.select { |entity| entity.is_a? Analyst::Entities::InstanceMethod }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
data/lib/analyst/parser.rb
CHANGED
|
@@ -1,76 +1,51 @@
|
|
|
1
1
|
require 'fileutils'
|
|
2
|
+
require 'pry'
|
|
2
3
|
|
|
3
4
|
module Analyst
|
|
4
5
|
|
|
5
6
|
class Parser
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
attr_reader :threshold, :formatter
|
|
9
|
-
attr_reader :start_time
|
|
8
|
+
extend Forwardable
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
attr_reader :start_path
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
@start_path = path
|
|
15
|
-
@formatter = formatter
|
|
16
|
-
@threshold = threshold
|
|
17
|
-
@start_time = Time.now
|
|
18
|
-
reset_output_directory
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def parsed_files
|
|
22
|
-
@parsed_files = source_files.map do |path_to_file|
|
|
23
|
-
parse_source_file(path_to_file)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def report
|
|
28
|
-
self.parsed_files.each do |file|
|
|
29
|
-
print "."
|
|
30
|
-
formatter.new(file, OUTPUT_DIRECTORY, file.source).export
|
|
31
|
-
end
|
|
32
|
-
puts
|
|
33
|
-
write_report_index
|
|
34
|
-
report_complexity
|
|
35
|
-
end
|
|
12
|
+
def_delegators :root, :classes, :top_level_classes
|
|
36
13
|
|
|
37
|
-
|
|
14
|
+
# TODO: Empty -> Unhandled (or something like that)
|
|
15
|
+
PROCESSORS = Hash.new(Entities::Empty).merge!(
|
|
16
|
+
:root => Entities::Root,
|
|
17
|
+
:class => Entities::Class,
|
|
18
|
+
:def => Entities::InstanceMethod,
|
|
19
|
+
:defs => Entities::SingletonMethod,
|
|
20
|
+
:begin => Entities::Begin,
|
|
21
|
+
:module => Entities::Module,
|
|
22
|
+
:send => Entities::MethodCall,
|
|
23
|
+
:sclass => Entities::SingletonClass
|
|
24
|
+
# :def => :method_node_parser,
|
|
25
|
+
# :send => :send_node_parser
|
|
26
|
+
# TODO: make a method parser, which pushes the the context_stack so that things inside method bodies
|
|
27
|
+
# are treated differently than those inside class or module bodies. same with Block (right?)
|
|
28
|
+
)
|
|
38
29
|
|
|
39
|
-
def
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
rescue Errno::ENOENT
|
|
43
|
-
end
|
|
44
|
-
FileUtils.mkpath(OUTPUT_DIRECTORY)
|
|
30
|
+
def self.process_node(node, parent)
|
|
31
|
+
return if node.nil? # TODO: maybe a Entities:Nil would be appropriate? maybe?
|
|
32
|
+
PROCESSORS[node.type].new(node, parent)
|
|
45
33
|
end
|
|
46
34
|
|
|
47
|
-
def
|
|
48
|
-
|
|
49
|
-
return Dir.glob(File.join(start_path, "**", "*.rb"))
|
|
50
|
-
else
|
|
51
|
-
return [start_path]
|
|
52
|
-
end
|
|
35
|
+
def initialize(ast)
|
|
36
|
+
@ast = ast
|
|
53
37
|
end
|
|
54
38
|
|
|
55
|
-
def
|
|
56
|
-
|
|
39
|
+
def inspect
|
|
40
|
+
"\#<#{self.class}:#{object_id}>"
|
|
57
41
|
end
|
|
58
42
|
|
|
59
|
-
|
|
60
|
-
return if self.threshold == 0
|
|
61
|
-
complexities = self.parsed_files.map(&:complexity)
|
|
62
|
-
return if complexities.max.to_i <= self.threshold
|
|
63
|
-
puts "Maximum complexity of #{complexities.max} exceeds #{options['threshold']} threshold!"
|
|
64
|
-
exit 1
|
|
65
|
-
end
|
|
43
|
+
private
|
|
66
44
|
|
|
67
|
-
def
|
|
68
|
-
|
|
69
|
-
puts "Results written to #{OUTPUT_DIRECTORY} "
|
|
70
|
-
return unless self.formatter.has_index?
|
|
71
|
-
formatter.index_class.new(parsed_files.map(&:summary), OUTPUT_DIRECTORY).export
|
|
45
|
+
def root
|
|
46
|
+
@root ||= self.class.process_node(@ast, nil)
|
|
72
47
|
end
|
|
73
48
|
|
|
74
49
|
end
|
|
75
50
|
|
|
76
|
-
end
|
|
51
|
+
end
|
data/lib/analyst/version.rb
CHANGED
data/lib/analyst.rb
CHANGED
|
@@ -4,17 +4,48 @@ require 'fileutils'
|
|
|
4
4
|
require 'haml'
|
|
5
5
|
|
|
6
6
|
require_relative "analyst/analyzer"
|
|
7
|
-
require_relative "analyst/
|
|
8
|
-
require_relative "analyst/
|
|
9
|
-
require_relative "analyst/
|
|
10
|
-
require_relative "analyst/
|
|
11
|
-
require_relative "analyst/
|
|
12
|
-
require_relative "analyst/
|
|
13
|
-
require_relative "analyst/
|
|
14
|
-
require_relative "analyst/
|
|
15
|
-
require_relative "analyst/
|
|
7
|
+
require_relative "analyst/entities/entity"
|
|
8
|
+
require_relative "analyst/entities/empty"
|
|
9
|
+
require_relative "analyst/entities/root"
|
|
10
|
+
require_relative "analyst/entities/begin"
|
|
11
|
+
require_relative "analyst/entities/module"
|
|
12
|
+
require_relative "analyst/entities/class"
|
|
13
|
+
require_relative "analyst/entities/method"
|
|
14
|
+
require_relative "analyst/entities/method_call"
|
|
15
|
+
require_relative "analyst/entities/singleton_class"
|
|
16
16
|
require_relative "analyst/parser"
|
|
17
17
|
require_relative "analyst/version"
|
|
18
18
|
|
|
19
19
|
module Analyst
|
|
20
|
+
def self.new(path_to_files)
|
|
21
|
+
Analyst::Parser.new(FileProcessor.new(path_to_files).ast)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class FileProcessor
|
|
25
|
+
|
|
26
|
+
attr_reader :path_to_files
|
|
27
|
+
|
|
28
|
+
def initialize(path_to_files)
|
|
29
|
+
@path_to_files = path_to_files
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def source_files
|
|
33
|
+
if File.directory?(path_to_files)
|
|
34
|
+
return Dir.glob(File.join(path_to_files, "**", "*.rb"))
|
|
35
|
+
else
|
|
36
|
+
return [path_to_files]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def ast
|
|
41
|
+
::Parser::AST::Node.new(
|
|
42
|
+
:root, source_files.map do |file|
|
|
43
|
+
content = File.open(file, "r").read
|
|
44
|
+
::Parser::CurrentRuby.parse(content)
|
|
45
|
+
end
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
|
|
20
51
|
end
|
data/spec/class_spec.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Analyst::Entities::Class do
|
|
4
|
+
|
|
5
|
+
let(:parser) { Analyst.new("./spec/fixtures/music.rb") }
|
|
6
|
+
let(:artist) { parser.classes.detect { |klass| klass.full_name == "Artist" } }
|
|
7
|
+
let(:singer) { parser.classes.detect { |klass| klass.full_name == "Singer" } }
|
|
8
|
+
|
|
9
|
+
describe "#method_calls" do
|
|
10
|
+
it "lists all method invocations within a class definition" do
|
|
11
|
+
macro_names = artist.macros.map(&:name)
|
|
12
|
+
expect(macro_names).to match_array ["attr_accessor"]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe "#imethods" do
|
|
17
|
+
it "returns a list of instance methods" do
|
|
18
|
+
method_names = artist.imethods.map(&:name)
|
|
19
|
+
expect(method_names).to match_array ["initialize", "starve"]
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe "#cmethods" do
|
|
24
|
+
it "returns a list of class methods" do
|
|
25
|
+
class_method_names = singer.cmethods.map(&:name)
|
|
26
|
+
expect(class_method_names).to match_array ["superstar", "sellouts"]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
class Artist
|
|
2
|
+
|
|
3
|
+
attr_accessor :stage_name
|
|
4
|
+
|
|
5
|
+
def initialize(name, attrs)
|
|
6
|
+
@name = name
|
|
7
|
+
@attrs = attrs
|
|
8
|
+
@stage_name = attrs[:stage_name]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def starve
|
|
12
|
+
Song.produce(10)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class Singer < Artist
|
|
17
|
+
|
|
18
|
+
SUPER_ATTRS = { singing: 10, dancing: 10, looks: 10 }
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
def sellouts
|
|
22
|
+
where(:album_sales > 10)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.superstar
|
|
27
|
+
first = %w[Michael Beyonce Cee-lo Devin]
|
|
28
|
+
last = %w[Jackson Knowles Green Townsend]
|
|
29
|
+
|
|
30
|
+
new("#{first.sample} #{last.sample}", SUPER_ATTRS.dup)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def sing
|
|
34
|
+
"♬ Hang the DJ! ♬"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class Song
|
|
39
|
+
|
|
40
|
+
def initialize(popularity)
|
|
41
|
+
@popularity = popularity
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module Instruments
|
|
46
|
+
|
|
47
|
+
class Stringed
|
|
48
|
+
|
|
49
|
+
def initialize(num_strings)
|
|
50
|
+
@num_strings = num_strings
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class Guitar < Stringed
|
|
55
|
+
|
|
56
|
+
def initialize(sound)
|
|
57
|
+
super(6)
|
|
58
|
+
@sound = sound
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
module Performances
|
|
65
|
+
module Equipment
|
|
66
|
+
class Amp
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|