analyst 0.14.2 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2741a5ba1447921d472b3a13e573b8d35bf1d39c
4
- data.tar.gz: 6db0b7297d497d41d406c2b67cf1d760cb751d76
3
+ metadata.gz: 7b73349858dc8cc8e23a7bcee486149fa81fa14b
4
+ data.tar.gz: e8d736fd3c97802f6a8bf7db970ae730385938e0
5
5
  SHA512:
6
- metadata.gz: 5d4f07efff500a960139db14696b1d35bd85321ee83f0f178a708d20fa80ca4ec1c5c49b9e83d41d30611d9610aacc3743924737d410ec875e5103ec5c31b667
7
- data.tar.gz: b18f69c1fa2cfc2357ef97c3ad6ee0685e6a1395d333dfceefc1fdbb76751b29ec624e20c4411e277e176945dabc95f7183de3de6c60df89945a02fce0aa3129
6
+ metadata.gz: 121c7e0d71706490dd637a48d718002a217b4fa2c190708ed9cd8c8d2fef02758cb118f0704afbd3fa397922571f09fa9270ee2dd53eb5b68266678309d9b0cb
7
+ data.tar.gz: 6306167886e6d7f9e4d1b6ff3bff588525805d18a6aec93b03f0c85164def809851bcfa0c54e617002813a52f43ab5a354f9e8da3f8300dadd7c71ff00539d0f
data/analyst.gemspec CHANGED
@@ -18,8 +18,6 @@ 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", ">= 2.4.0"
22
- spec.add_dependency "poro_plus"
23
21
  spec.add_dependency "haml"
24
22
  spec.add_dependency "parser"
25
23
  spec.add_dependency "rainbow"
@@ -32,5 +30,8 @@ Gem::Specification.new do |spec|
32
30
  spec.add_development_dependency "rspec"
33
31
  spec.add_development_dependency "simplecov"
34
32
  spec.add_development_dependency "pry"
33
+ spec.add_development_dependency "byebug"
34
+ spec.add_development_dependency "awesome_print"
35
35
 
36
36
  end
37
+
@@ -0,0 +1,15 @@
1
+ module Analyst
2
+
3
+ module Entities
4
+ class Array < Entity
5
+
6
+ handles_node :array
7
+
8
+ private
9
+
10
+ def contents
11
+ @contents ||= ast.children.map { |child| process_node(child) }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -8,8 +8,14 @@ module Analyst
8
8
  module Entities
9
9
  class Class < Analyst::Entities::Module
10
10
 
11
+ handles_node :class
12
+
11
13
  alias :macros :method_calls
12
14
 
15
+ def kind
16
+ "Class"
17
+ end
18
+
13
19
  def imethods
14
20
  @imethods ||= contents.select { |entity| entity.is_a? Analyst::Entities::InstanceMethod }
15
21
  end
@@ -36,65 +42,6 @@ module Analyst
36
42
  end
37
43
  end
38
44
 
39
- # ASSOCIATIONS = [:belongs_to, :has_one, :has_many, :has_and_belongs_to_many]
40
-
41
- # attr_reader :associations
42
-
43
- # def initialize(node, parent)
44
- # @associations = []
45
- # super
46
- # end
47
-
48
- # def handle_send(node)
49
- # target, method_name, *args = node.children
50
- # if ASSOCIATIONS.include?(method_name)
51
- # add_association(method_name, args)
52
- # end
53
- # end
54
-
55
- # # When a class is reopened, merge its associations
56
- # def merge_associations_from(klass)
57
- # klass.associations.each do |association|
58
- # associations << Association.new(
59
- # type: association.type,
60
- # source: self,
61
- # target_class: association.target_class
62
- # )
63
- # end
64
- # associations.uniq!
65
- # end
66
-
67
- # private
68
-
69
- # def add_association(method_name, args)
70
- # target_class = value_from_hash_node(args.last, :class_name)
71
- # target_class ||= begin
72
- # symbol_node = args.first
73
- # symbol_name = symbol_node.children.first
74
- # symbol_name.pluralize.classify
75
- # end
76
- # association = Association.new(type: method_name, source: self, target_class: target_class)
77
- # associations << association
78
- # end
79
-
80
- # private
81
-
82
- # # Fetches value from hash node iff key is symbol and value is str
83
- # # Raises an exception if value is not str
84
- # # Returns nil if key is not found
85
- # def value_from_hash_node(node, key)
86
- # return unless node.type == :hash
87
- # pair = node.children.detect do |pair_node|
88
- # key_symbol_node = pair_node.children.first
89
- # key == key_symbol_node.children.first
90
- # end
91
- # if pair
92
- # value_node = pair.children.last
93
- # throw "Bad type. Expected (str), got (#{value_node.type})" unless value_node.type == :str
94
- # value_node.children.first
95
- # end
96
- # end
97
-
98
45
  end
99
46
 
100
47
  end
@@ -0,0 +1,18 @@
1
+ module Analyst
2
+
3
+ module Entities
4
+ class CodeBlock < Entity
5
+ extend Forwardable
6
+
7
+ handles_node :begin
8
+
9
+ def_delegators :parent, :name, :full_name
10
+
11
+ def contents
12
+ @contents ||= ast.children.map { |child| process_node(child) }
13
+ end
14
+
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,19 @@
1
+ module Analyst
2
+
3
+ module Entities
4
+ class Conditional < Entity
5
+
6
+ handles_node :if
7
+ handles_node :or
8
+ handles_node :and
9
+ handles_node :or_asgn
10
+ handles_node :and_asgn
11
+
12
+ private
13
+
14
+ def contents
15
+ @contents ||= ast.children.map { |child| process_node(child) }.compact
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,41 @@
1
+ module Analyst
2
+
3
+ module Entities
4
+ class Constant < Entity
5
+
6
+ handles_node :const
7
+
8
+ def name
9
+ const_node_array(ast).join("::")
10
+ end
11
+
12
+ def full_name
13
+ name
14
+ end
15
+
16
+ def constants
17
+ []
18
+ end
19
+
20
+ private
21
+
22
+ # takes a (const) node and returns an array specifying the fully-qualified
23
+ # constant name that it represents. ya know, so CoolModule::SubMod::SweetClass
24
+ # would be parsed to:
25
+ # (const
26
+ # (const
27
+ # (const nil :CoolModule) :SubMod) :SweetClass)
28
+ # and passing that node here would return [:CoolModule, :SubMod, :SweetClass]
29
+ # TODO: should really be nested Entities::Constants all the way down.
30
+ # ((cbase) can probably use the same Entity, or maybe it's a subclass of Constant)
31
+ def const_node_array(node)
32
+ return [] if node.nil?
33
+ return [''] if node.type == :cbase
34
+ raise "expected (const) or (cbase) node or nil, got (#{node.type})" unless node.type == :const
35
+ const_node_array(node.children.first) << node.children[1]
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+
@@ -1,21 +1,21 @@
1
+ require_relative '../processor'
2
+
1
3
  # An entity is a named node of a given type which may have additional properties
2
4
  module Analyst
3
5
  module Entities
4
6
  class Entity
5
7
 
6
- attr_reader :parent
8
+ attr_reader :parent, :ast
9
+
10
+ def self.handles_node(type)
11
+ Analyst::Processor.register_processor(type, self)
12
+ end
7
13
 
8
14
  def initialize(ast, parent)
9
15
  @parent = parent
10
16
  @ast = ast
11
17
  end
12
18
 
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
19
  def classes
20
20
  @classes ||= begin
21
21
  nested_classes = top_level_classes.map(&:classes).flatten
@@ -24,6 +24,21 @@ module Analyst
24
24
  end
25
25
  end
26
26
 
27
+ def modules
28
+ @modules ||= begin
29
+ nested_modules = top_level_modules.map(&:modules).flatten
30
+ top_level_modules + nested_modules
31
+ end
32
+ end
33
+
34
+ def constants
35
+ @constants ||= top_level_constants + contents.map(&:constants).flatten
36
+ end
37
+
38
+ def top_level_constants
39
+ @top_level_constants ||= contents_of_type(Entities::Constant)
40
+ end
41
+
27
42
  def top_level_modules
28
43
  @top_level_modules ||= contents_of_type(Entities::Module)
29
44
  end
@@ -36,26 +51,67 @@ module Analyst
36
51
  @method_calls ||= contents_of_type(Entities::MethodCall)
37
52
  end
38
53
 
54
+ # TODO: rethink the different kinds of Methods. there's really only one
55
+ # kind of Method, right??
56
+ # ref: http://www.devalot.com/articles/2008/09/ruby-singleton
57
+ def methods
58
+ @methods ||= contents_of_type(Entities::InstanceMethod)
59
+ end
60
+
61
+ def conditionals
62
+ @conditionals ||= contents_of_type(Entities::Conditional)
63
+ end
64
+
65
+ def location
66
+ "#{file_path}:#{line_number}"
67
+ end
68
+
69
+ def file_path
70
+ parent.file_path
71
+ end
72
+
73
+ def line_number
74
+ ast.loc.line
75
+ end
76
+
77
+ def source
78
+ origin_source[source_range]
79
+ end
80
+
81
+ def origin_source
82
+ parent.origin_source
83
+ end
84
+
39
85
  def full_name
40
86
  throw "Subclass #{self.class.name} must implement #full_name"
41
87
  end
42
88
 
43
89
  def inspect
44
- "\#<#{self.class}:#{object_id} full_name=#{full_name}>"
90
+ "\#<#{self.class} location=#{location} full_name=#{full_name}>"
45
91
  rescue
46
- "\#<#{self.class}:#{object_id}>"
92
+ "\#<#{self.class} location=#{location}>"
47
93
  end
48
94
 
49
95
  private
50
96
 
51
- attr_reader :ast
97
+ def source_range
98
+ Range.new(ast.loc.expression.begin_pos, ast.loc.expression.end_pos)
99
+ end
52
100
 
53
101
  def contents_of_type(klass)
54
102
  contents.select { |entity| entity.is_a? klass }
55
103
  end
56
104
 
57
105
  def contents
58
- @contents ||= Array(Analyst::Parser.process_node(content_node, self))
106
+ @contents ||= if actual_contents.is_a? Entities::CodeBlock
107
+ actual_contents.contents
108
+ else
109
+ Array(actual_contents)
110
+ end
111
+ end
112
+
113
+ def actual_contents
114
+ @actual_contents ||= process_node(content_node)
59
115
  end
60
116
 
61
117
  def content_node
@@ -63,7 +119,7 @@ module Analyst
63
119
  end
64
120
 
65
121
  def process_node(node, parent=self)
66
- Analyst::Parser.process_node(node, parent) }
122
+ Analyst::Processor.process_node(node, parent)
67
123
  end
68
124
 
69
125
  def process_nodes(nodes, parent=self)
@@ -0,0 +1,42 @@
1
+ module Analyst
2
+
3
+ module Entities
4
+
5
+ class File < Entity
6
+
7
+ handles_node :analyst_file
8
+
9
+ def full_name
10
+ ""
11
+ end
12
+
13
+ def file_path
14
+ parent.source_data_for(self)
15
+ end
16
+
17
+ def origin_source
18
+ ::File.open(file_path, 'r').read
19
+ end
20
+
21
+ def contents
22
+ @contents ||= actual_contents.map do |child|
23
+ # skip top-level CodeBlocks
24
+ child.is_a?(Entities::CodeBlock) ? child.contents : child
25
+ end.flatten
26
+ end
27
+
28
+ private
29
+
30
+ def source_range
31
+ 0..-1
32
+ end
33
+
34
+ def actual_contents
35
+ @actual_contents ||= ast.children.map { |child| process_node(child) }
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -2,13 +2,26 @@ module Analyst
2
2
 
3
3
  module Entities
4
4
  class Hash < Entity
5
+
6
+ handles_node :hash
7
+
5
8
  def pairs
6
9
  @pairs ||= process_nodes(ast.children)
7
10
  end
8
11
 
9
- def to_hash
10
- @pairs.inject({}) do |hash, pair|
11
- hash[pair.key] = pair.value
12
+ # Convenience method to turn this Entity into an actual ::Hash.
13
+ # If `extract_values` is true, then all keys and values that respond
14
+ # to `#value` will be replaced by the value they return from that call.
15
+ # Otherwise they'll be left as Analyst::Entities::Entity objects.
16
+ def to_hash(extract_values:true)
17
+ pairs.inject({}) do |hash, pair|
18
+ key = pair.key
19
+ val = pair.value
20
+ if extract_values
21
+ key = key.value if key.respond_to?(:value)
22
+ val = val.value if val.respond_to?(:value)
23
+ end
24
+ hash[key] = val
12
25
  hash
13
26
  end
14
27
  end
@@ -2,6 +2,9 @@ module Analyst
2
2
  module Entities
3
3
 
4
4
  class InterpolatedString < Entity
5
+
6
+ handles_node :dstr
7
+
5
8
  def name
6
9
  throw "Needs implementation"
7
10
  end
@@ -2,19 +2,31 @@ module Analyst
2
2
  module Entities
3
3
 
4
4
  class InstanceMethod < Entity
5
+
6
+ handles_node :def
7
+
8
+ def kind
9
+ "Instance Method"
10
+ end
11
+
5
12
  def name
6
13
  ast.children.first.to_s
7
14
  end
15
+
8
16
  def full_name
9
17
  parent.full_name + '#' + name
10
18
  end
11
19
  end
12
20
 
13
- # TODO HERE
14
21
  class ClassMethod < Entity
22
+ def kind
23
+ "Class Method"
24
+ end
25
+
15
26
  def name
16
27
  ast.children.first.to_s
17
28
  end
29
+
18
30
  def full_name
19
31
  parent.full_name + '::' + name
20
32
  end
@@ -22,6 +34,8 @@ module Analyst
22
34
 
23
35
  class SingletonMethod < Entity
24
36
 
37
+ handles_node :defs
38
+
25
39
  # NOTE: not a public API -- used by Entities::Class
26
40
  def target
27
41
  target, name, params, content = ast.children
@@ -2,6 +2,8 @@ module Analyst
2
2
  module Entities
3
3
  class MethodCall < Entity
4
4
 
5
+ handles_node :send
6
+
5
7
  def name
6
8
  name_node.to_s
7
9
  end
@@ -13,17 +15,31 @@ module Analyst
13
15
  def arguments
14
16
  @arguments ||= begin
15
17
  args = ast.children[2..-1]
16
- args.map { |arg| Analyst::Parser.process_node(arg, self) }
18
+ args.map { |arg| process_node(arg) }
17
19
  end
18
20
  end
19
21
 
20
- # TODO: figure out how to resolve this to an Entity. we never want
21
- # to expose the AST to the outside.
22
+ def constants
23
+ if target.is_a? Analyst::Entities::Constant
24
+ super << target
25
+ else
26
+ super
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def contents
33
+ arguments
34
+ end
35
+
22
36
  def target_node
23
37
  ast.children.first
24
38
  end
25
39
 
26
- private
40
+ def target
41
+ process_node(target_node)
42
+ end
27
43
 
28
44
  def name_node
29
45
  ast.children[1]
@@ -2,8 +2,14 @@ module Analyst
2
2
  module Entities
3
3
  class Module < Entity
4
4
 
5
+ handles_node :module
6
+
7
+ def kind
8
+ "Module"
9
+ end
10
+
5
11
  def name
6
- const_node_array(name_node).join('::')
12
+ name_entity.name
7
13
  end
8
14
 
9
15
  def full_name
@@ -12,21 +18,12 @@ module Analyst
12
18
 
13
19
  private
14
20
 
15
- def name_node
16
- ast.children.first
21
+ def name_entity
22
+ @name_entity ||= process_node(name_node)
17
23
  end
18
24
 
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]
25
+ def name_node
26
+ ast.children.first
30
27
  end
31
28
  end
32
29
  end
@@ -2,6 +2,9 @@ module Analyst
2
2
 
3
3
  module Entities
4
4
  class Pair < Entity
5
+
6
+ handles_node :pair
7
+
5
8
  def key
6
9
  process_node(ast.children[0])
7
10
  end
@@ -1,19 +1,43 @@
1
1
  module Analyst
2
2
 
3
3
  module Entities
4
+
4
5
  class Root < Entity
5
6
 
7
+ handles_node :analyst_root
8
+
9
+ def initialize(ast, source_data)
10
+ @source_data = source_data
11
+ super(ast, nil)
12
+ end
13
+
6
14
  def full_name
7
15
  ""
8
16
  end
9
17
 
18
+ def source_data_for(entity)
19
+ source_data[actual_contents.index(entity)]
20
+ end
21
+
22
+ def file_path
23
+ throw "Entity tree malformed - Source or File should have caught this call"
24
+ end
25
+
26
+ def origin_source
27
+ throw "Entity tree malformed - Source or File hsould have caught this call"
28
+ end
29
+
10
30
  private
11
31
 
32
+ attr_reader :source_data
33
+
12
34
  def contents
13
- @contents ||= begin
14
- child_nodes = ast.children
15
- child_nodes.map { |child| Analyst::Parser.process_node(child, self) }.flatten
16
- end
35
+ # skip all top-level entities, cuz they're all Files and Sources
36
+ @contents ||= actual_contents.map(&:contents).flatten
37
+ end
38
+
39
+ def actual_contents
40
+ @actual_contents ||= ast.children.map { |child| process_node(child) }
17
41
  end
18
42
 
19
43
  end
@@ -3,6 +3,8 @@ module Analyst
3
3
  module Entities
4
4
  class SingletonClass < Entity
5
5
 
6
+ handles_node :sclass
7
+
6
8
  def full_name
7
9
  parent.full_name + "!SINGLETON"
8
10
  end
@@ -0,0 +1,42 @@
1
+ module Analyst
2
+
3
+ module Entities
4
+
5
+ class Source < Entity
6
+
7
+ handles_node :analyst_source
8
+
9
+ def full_name
10
+ ""
11
+ end
12
+
13
+ def file_path
14
+ "$PARSED FROM SOURCE$"
15
+ end
16
+
17
+ def origin_source
18
+ parent.source_data_for(self)
19
+ end
20
+
21
+ def contents
22
+ @contents ||= actual_contents.map do |child|
23
+ # skip top-level CodeBlocks
24
+ child.is_a?(Entities::CodeBlock) ? child.contents : child
25
+ end.flatten
26
+ end
27
+
28
+ private
29
+
30
+ def source_range
31
+ 0..-1
32
+ end
33
+
34
+ def actual_contents
35
+ @actual_contents ||= ast.children.map { |child| process_node(child) }
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -1,12 +1,25 @@
1
1
  module Analyst
2
2
 
3
3
  module Entities
4
- module String
4
+ class String < Entity
5
5
 
6
- def self.new(node, parent)
6
+ handles_node :str
7
+
8
+ def value
7
9
  ast.children.first
8
10
  end
9
11
 
12
+ def name
13
+ end
14
+
15
+ def full_name
16
+ end
17
+
18
+ private
19
+
20
+ def content_node
21
+ end
22
+
10
23
  end
11
24
  end
12
25
  end