wool 0.5.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.
- data/.document +5 -0
- data/.gitignore +23 -0
- data/LICENSE +45 -0
- data/README.rdoc +17 -0
- data/Rakefile +77 -0
- data/TODO.md +17 -0
- data/VERSION +1 -0
- data/bin/wool +4 -0
- data/features/step_definitions/wool_steps.rb +39 -0
- data/features/support/env.rb +14 -0
- data/features/support/testdata/1_input +1 -0
- data/features/support/testdata/1_output +1 -0
- data/features/support/testdata/2_input +4 -0
- data/features/support/testdata/2_output +4 -0
- data/features/support/testdata/3_input +8 -0
- data/features/support/testdata/3_output +11 -0
- data/features/support/testdata/4_input +5 -0
- data/features/support/testdata/4_output +5 -0
- data/features/wool.feature +24 -0
- data/lib/wool.rb +40 -0
- data/lib/wool/advice/advice.rb +42 -0
- data/lib/wool/advice/comment_advice.rb +37 -0
- data/lib/wool/analysis/annotations.rb +34 -0
- data/lib/wool/analysis/annotations/next_annotation.rb +26 -0
- data/lib/wool/analysis/annotations/parent_annotation.rb +20 -0
- data/lib/wool/analysis/annotations/scope_annotation.rb +37 -0
- data/lib/wool/analysis/lexical_analysis.rb +165 -0
- data/lib/wool/analysis/protocol_registry.rb +32 -0
- data/lib/wool/analysis/protocols.rb +82 -0
- data/lib/wool/analysis/scope.rb +13 -0
- data/lib/wool/analysis/sexp_analysis.rb +98 -0
- data/lib/wool/analysis/signature.rb +16 -0
- data/lib/wool/analysis/symbol.rb +10 -0
- data/lib/wool/analysis/visitor.rb +36 -0
- data/lib/wool/analysis/wool_class.rb +47 -0
- data/lib/wool/rake/task.rb +42 -0
- data/lib/wool/runner.rb +156 -0
- data/lib/wool/scanner.rb +160 -0
- data/lib/wool/support/module_extensions.rb +84 -0
- data/lib/wool/third_party/trollop.rb +845 -0
- data/lib/wool/warning.rb +145 -0
- data/lib/wool/warnings/comment_spacing.rb +30 -0
- data/lib/wool/warnings/extra_blank_lines.rb +29 -0
- data/lib/wool/warnings/extra_whitespace.rb +15 -0
- data/lib/wool/warnings/line_length.rb +113 -0
- data/lib/wool/warnings/misaligned_unindentation.rb +16 -0
- data/lib/wool/warnings/operator_spacing.rb +63 -0
- data/lib/wool/warnings/rescue_exception.rb +41 -0
- data/lib/wool/warnings/semicolon.rb +24 -0
- data/lib/wool/warnings/useless_double_quotes.rb +37 -0
- data/spec/advice_specs/advice_spec.rb +69 -0
- data/spec/advice_specs/comment_advice_spec.rb +38 -0
- data/spec/advice_specs/spec_helper.rb +1 -0
- data/spec/analysis_specs/annotations_specs/next_prev_annotation_spec.rb +47 -0
- data/spec/analysis_specs/annotations_specs/parent_annotation_spec.rb +41 -0
- data/spec/analysis_specs/annotations_specs/spec_helper.rb +5 -0
- data/spec/analysis_specs/lexical_analysis_spec.rb +179 -0
- data/spec/analysis_specs/protocol_registry_spec.rb +58 -0
- data/spec/analysis_specs/protocols_spec.rb +49 -0
- data/spec/analysis_specs/scope_spec.rb +20 -0
- data/spec/analysis_specs/sexp_analysis_spec.rb +134 -0
- data/spec/analysis_specs/spec_helper.rb +2 -0
- data/spec/analysis_specs/visitor_spec.rb +53 -0
- data/spec/analysis_specs/wool_class_spec.rb +54 -0
- data/spec/rake_specs/spec_helper.rb +1 -0
- data/spec/rake_specs/task_spec.rb +67 -0
- data/spec/runner_spec.rb +171 -0
- data/spec/scanner_spec.rb +75 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +93 -0
- data/spec/support_specs/module_extensions_spec.rb +91 -0
- data/spec/support_specs/spec_helper.rb +1 -0
- data/spec/warning_spec.rb +95 -0
- data/spec/warning_specs/comment_spacing_spec.rb +57 -0
- data/spec/warning_specs/extra_blank_lines_spec.rb +70 -0
- data/spec/warning_specs/extra_whitespace_spec.rb +33 -0
- data/spec/warning_specs/line_length_spec.rb +165 -0
- data/spec/warning_specs/misaligned_unindentation_spec.rb +35 -0
- data/spec/warning_specs/operator_spacing_spec.rb +101 -0
- data/spec/warning_specs/rescue_exception_spec.rb +105 -0
- data/spec/warning_specs/semicolon_spec.rb +58 -0
- data/spec/warning_specs/spec_helper.rb +1 -0
- data/spec/warning_specs/useless_double_quotes_spec.rb +62 -0
- data/spec/wool_spec.rb +8 -0
- data/status_reports/2010/12/2010-12-14.md +163 -0
- data/test/third_party_tests/test_trollop.rb +1181 -0
- data/wool.gemspec +173 -0
- metadata +235 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module Wool
|
2
|
+
module SexpAnalysis
|
3
|
+
# This is the base module for all annotations that can run on ASTs.
|
4
|
+
# It includes all other annotation modules to provide all the
|
5
|
+
# annotations to the Sexp class. These annotations are run at initialize
|
6
|
+
# time for the Sexps and have access to a node and all of its child nodes,
|
7
|
+
# all of which have been annotated. Synthesized attributes are fair game,
|
8
|
+
# and adding inherited attributes to subnodes is also fair game.
|
9
|
+
#
|
10
|
+
# All annotations add O(V) to the parser runtime.
|
11
|
+
#
|
12
|
+
# This module also provides some helper methods to inject functionality into
|
13
|
+
# the Sexp class. Since that's what an annotation is, I don't consider
|
14
|
+
# this bad form!
|
15
|
+
module BasicAnnotation
|
16
|
+
def add_annotator(*args)
|
17
|
+
SexpAnalysis::Sexp.annotations.concat args.map(&:new)
|
18
|
+
end
|
19
|
+
alias_method :add_annotators, :add_annotator
|
20
|
+
def add_global_annotator(*args)
|
21
|
+
SexpAnalysis.global_annotations.concat args.map(&:new)
|
22
|
+
end
|
23
|
+
alias_method :add_global_annotators, :add_global_annotator
|
24
|
+
def add_property(*args)
|
25
|
+
SexpAnalysis::Sexp.__send__(:attr_accessor, *args)
|
26
|
+
end
|
27
|
+
alias_method :add_properties, :add_property
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
Dir[File.expand_path(File.join(File.dirname(__FILE__), 'annotations', '**', '*.rb'))].each do |file|
|
33
|
+
load file
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Wool
|
2
|
+
module SexpAnalysis
|
3
|
+
# This is a simple inherited attribute applied to each node,
|
4
|
+
# giving a pointer to that node's next and previous AST node.
|
5
|
+
# That way AST traversal is easier.
|
6
|
+
module NextPrevAnnotation
|
7
|
+
extend BasicAnnotation
|
8
|
+
add_properties :next, :prev
|
9
|
+
|
10
|
+
# This is the annotator for the next and prev annotation.
|
11
|
+
class Annotator
|
12
|
+
def annotate!(root)
|
13
|
+
children = root.children
|
14
|
+
children.each_with_index do |elt, idx|
|
15
|
+
# ignore non-sexps. Primitives can't be annotated, sadly.
|
16
|
+
if SexpAnalysis::Sexp === elt
|
17
|
+
elt.next = children[idx+1]
|
18
|
+
elt.prev = children[idx-1] if idx >= 1
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
add_annotator Annotator
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Wool
|
2
|
+
module SexpAnalysis
|
3
|
+
# This is a simple inherited attribute applied to each node,
|
4
|
+
# giving a pointer to that node's parent. That way AST traversal
|
5
|
+
# is easier.
|
6
|
+
module ParentAnnotation
|
7
|
+
extend BasicAnnotation
|
8
|
+
add_property :parent
|
9
|
+
|
10
|
+
# This is the annotator for the parent annotation.
|
11
|
+
class Annotator
|
12
|
+
def annotate!(root)
|
13
|
+
root.parent = nil
|
14
|
+
root.children.select {|x| SexpAnalysis::Sexp === x}.each {|sexp| sexp.parent = root}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
add_annotator Annotator
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Wool
|
2
|
+
module SexpAnalysis
|
3
|
+
# This is a *global* annotation, namely the one that determines the statically-known
|
4
|
+
# scope for each node in the AST, at the time of that node's execution. For
|
5
|
+
# example, every node should be able to say "hey scope, what's 'this' for this
|
6
|
+
# statement?", and be able to return its type (*NOT* its class, they're different).
|
7
|
+
module ScopeAnnotation
|
8
|
+
extend BasicAnnotation
|
9
|
+
add_property :scope
|
10
|
+
|
11
|
+
# This is the annotator for the parent annotation.
|
12
|
+
class Annotator
|
13
|
+
include Visitor
|
14
|
+
def annotate!(root)
|
15
|
+
@current_scope = Scope::GlobalScope
|
16
|
+
visit(root)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Replaces the general node visit method with one that assigns
|
20
|
+
# the current scope to the visited node.
|
21
|
+
def default_visit(node)
|
22
|
+
node.scope = @current_scope
|
23
|
+
end
|
24
|
+
|
25
|
+
def visit_module(node)
|
26
|
+
path_node, body = node.children
|
27
|
+
end
|
28
|
+
|
29
|
+
def visit_class(node)
|
30
|
+
path_to_new_class, superclass, body = node.children
|
31
|
+
superclass = superclass ? superclass.eval_as_constant(@current_scope) : ClassRegistry['Object']
|
32
|
+
end
|
33
|
+
end
|
34
|
+
add_global_annotator Annotator
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module Wool
|
2
|
+
# This is a set of methods that get provided to Warnings so they can perform
|
3
|
+
# lexical analysis of their bodies. This module handles tokenizing only - not
|
4
|
+
# parse-trees.
|
5
|
+
module LexicalAnalysis
|
6
|
+
# This is a wrapper class around the tokens returned by Ripper. Since the
|
7
|
+
# tokens are just arrays, this class lets us use nice mnemonics with almost zero
|
8
|
+
# runtime overhead.
|
9
|
+
class Token < Struct.new(:type, :body, :line, :col)
|
10
|
+
# Unpacks the token from Ripper and breaks it into its separate components.
|
11
|
+
#
|
12
|
+
# @param [Array<Array<Integer, Integer>, Symbol, String>] token the token
|
13
|
+
# from Ripper that we're wrapping
|
14
|
+
def initialize(token)
|
15
|
+
pos, self.type, self.body = token
|
16
|
+
self.line, self.col = pos
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Lexes the given text.
|
21
|
+
#
|
22
|
+
# @param [String] body (self.body) The text to lex
|
23
|
+
# @return [Array<Array<Integer, Integer>, Symbol, String>] A set of tokens
|
24
|
+
# in Ripper's result format. Each token is an array of the form:
|
25
|
+
# [[1, token_position], token_type, token_text]. I'm not exactly clear on
|
26
|
+
# why the 1 is always there. At any rate - the result is an array of those
|
27
|
+
# tokens.
|
28
|
+
def lex(body = self.body)
|
29
|
+
Ripper.lex(body).map {|token| Token.new(token) }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the text between two token positions. The token positions are
|
33
|
+
# in [line, column] format. The body, left, and right tokens must be provided,
|
34
|
+
# and optionally, you can override the inclusiveness of the text-between operation.
|
35
|
+
# It defaults to :none, for including neither the left nor right tokens in the
|
36
|
+
# result. You can pass :none, :left, :right, or :both.
|
37
|
+
#
|
38
|
+
# @param [String] body (self.body) The first parameter is optional: the text
|
39
|
+
# to search. This defaults to the full text.
|
40
|
+
# @param [Token] left the left token to get the text between
|
41
|
+
# @param [Token] right the right token to get the text between
|
42
|
+
# @param [Symbol] inclusive should the :left, :right, :both, or :none tokens
|
43
|
+
# be included in the resulting text?
|
44
|
+
# @return the text between the two tokens within the text. This is necessary
|
45
|
+
# because the lexer provides [line, column] coordinates which is quite
|
46
|
+
# unfortunate.
|
47
|
+
def text_between_token_positions(text, left, right, inclusive = :none)
|
48
|
+
result = ""
|
49
|
+
lines = text.lines.to_a
|
50
|
+
left.line.upto(right.line) do |cur_line|
|
51
|
+
line = lines[cur_line - 1]
|
52
|
+
result << left.body if cur_line == left.line && (inclusive == :both || inclusive == :left)
|
53
|
+
left_bound = cur_line == left.line ? left.col + left.body.size : 0
|
54
|
+
right_bound = cur_line == right.line ? right.col - 1 : -1
|
55
|
+
result << line[left_bound..right_bound]
|
56
|
+
result << right.body if cur_line == right.line && (inclusive == :both || inclusive == :right)
|
57
|
+
end
|
58
|
+
result
|
59
|
+
end
|
60
|
+
|
61
|
+
# Searches for the given token using standard [body], target symbols syntax.
|
62
|
+
# Yields for each token found that matches the query, and returns all those
|
63
|
+
# who match.
|
64
|
+
#
|
65
|
+
# @param [String] body (self.body) The first parameter is optional: the text
|
66
|
+
# to search. This defaults to the full text.
|
67
|
+
# @param [Symbol] token The rest of the arguments are tokens to search
|
68
|
+
# for. Any number of tokens may be specified.
|
69
|
+
# @return [Array<Array>] All the matching tokens for the query
|
70
|
+
def select_token(*args)
|
71
|
+
body, list = _extract_token_search_args(args)
|
72
|
+
result = []
|
73
|
+
while (token = find_token(body, *list)) && token != nil
|
74
|
+
result << token if yield(*token)
|
75
|
+
_, body = split_on_token(body, *list)
|
76
|
+
body = body[token.body.size..-1]
|
77
|
+
end
|
78
|
+
return result
|
79
|
+
end
|
80
|
+
|
81
|
+
# Finds the first instance of a set of keywords in the body. If no text is
|
82
|
+
# given to scan, then the full content is scanned.
|
83
|
+
#
|
84
|
+
# @param [String] body (self.body) The first parameter is optional: the text
|
85
|
+
# to search. This defaults to the full text.
|
86
|
+
# @param [Symbol] keyword The rest of the arguments are keywords to search
|
87
|
+
# for. Any number of keywords may be specified.
|
88
|
+
# @return [Array] the token in the form returned by Ripper. See #lex.
|
89
|
+
def find_keyword(*args)
|
90
|
+
body, list = _extract_token_search_args(args)
|
91
|
+
list.map! {|x| x.to_s}
|
92
|
+
lexed = lex(body)
|
93
|
+
lexed.find.with_index do |tok, idx|
|
94
|
+
is_keyword = tok.type == :on_kw && list.include?(tok.body)
|
95
|
+
is_not_symbol = idx == 0 || lexed[idx-1].type != :on_symbeg
|
96
|
+
is_keyword && is_not_symbol
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Finds the first instance of a set of tokens in the body. If no text is
|
101
|
+
# given to scan, then the full content is scanned.
|
102
|
+
#
|
103
|
+
# @param [String] body (self.body) The first parameter is optional: the text
|
104
|
+
# to search. This defaults to the full text.
|
105
|
+
# @param [Symbol] token The rest of the arguments are tokens to search
|
106
|
+
# for. Any number of tokens may be specified.
|
107
|
+
# @return [Array] the token in the form returned by Ripper. See #lex.
|
108
|
+
def find_token(*args)
|
109
|
+
body, list = _extract_token_search_args(args)
|
110
|
+
lexed = lex(body)
|
111
|
+
lexed.find.with_index do |tok, idx|
|
112
|
+
is_token = list.include?(tok.type)
|
113
|
+
is_not_symbol = idx == 0 || lexed[idx-1].type != :on_symbeg
|
114
|
+
is_token && is_not_symbol
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Splits the body into two halfs based on the first appearance of a keyword.
|
119
|
+
#
|
120
|
+
# @example
|
121
|
+
# split_on_keyword('x = 5 unless y == 2', :unless)
|
122
|
+
# # => ['x = 5 ', 'unless y == 2']
|
123
|
+
# @param [String] body (self.body) The first parameter is optional: the text
|
124
|
+
# to search. This defaults to the full text.
|
125
|
+
# @param [Symbol] token The rest of the arguments are keywords to search
|
126
|
+
# for. Any number of keywords may be specified.
|
127
|
+
# @return [Array<String, String>] The body split by the keyword.
|
128
|
+
def split_on_keyword(*args)
|
129
|
+
body, keywords = _extract_token_search_args(args)
|
130
|
+
token = find_keyword(body, *keywords)
|
131
|
+
return _split_body_with_raw_token(body, token)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Splits the body into two halfs based on the first appearance of a token.
|
135
|
+
#
|
136
|
+
# @example
|
137
|
+
# split_on_token('x = 5 unless y == 2', :on_kw)
|
138
|
+
# # => ['x = 5 ', 'unless y == 2']
|
139
|
+
# @param [String] body (self.body) The first parameter is optional: the text
|
140
|
+
# to search. This defaults to the full text.
|
141
|
+
# @param [Symbol] token The rest of the arguments are tokens to search
|
142
|
+
# for. Any number of tokens may be specified.
|
143
|
+
# @return [Array<String, String>] The body split by the token.
|
144
|
+
def split_on_token(*args)
|
145
|
+
body, tokens = _extract_token_search_args(args)
|
146
|
+
token = find_token(body, *tokens)
|
147
|
+
return _split_body_with_raw_token(body, token)
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def _extract_token_search_args(args)
|
153
|
+
if args.first.is_a?(String)
|
154
|
+
return args[0], args[1..-1]
|
155
|
+
else
|
156
|
+
return self.body, args
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def _split_body_with_raw_token(body, token)
|
161
|
+
max = token ? [0, token.col].max : body.size
|
162
|
+
return body[0,max], body[max..-1]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Wool
|
2
|
+
module SexpAnalysis
|
3
|
+
# The ProtocolRegistry module handles negotiating instantiated protocols at
|
4
|
+
# compile-time, such as the automatically-generated protocols created by a
|
5
|
+
# class creation (as each class has a corresponding protocol, though some
|
6
|
+
# distinct classes may have equivalent protocols).
|
7
|
+
module ProtocolRegistry
|
8
|
+
extend ModuleExtensions
|
9
|
+
cattr_accessor_with_default :protocols, []
|
10
|
+
cattr_accessor_with_default :class_protocols, {}
|
11
|
+
|
12
|
+
def self.add_protocol(proto)
|
13
|
+
self.protocols << proto
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.add_class_protocol(class_protocol)
|
17
|
+
add_protocol class_protocol
|
18
|
+
self.class_protocols[class_protocol.class_used.path] = class_protocol
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.[](class_name)
|
22
|
+
return query(:class_path => class_name.gsub(/^::/, ''))
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.query(query={})
|
26
|
+
if query[:class_path]
|
27
|
+
return [self.class_protocols[query[:class_path]]]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Wool
|
2
|
+
module SexpAnalysis
|
3
|
+
# This module contains a class hierarchy that represents the type
|
4
|
+
# hierarchy in the analyzed Ruby code. This is where method responsiveness
|
5
|
+
# can be checked. Some protocols are simple, such as a ClassProtocol,
|
6
|
+
# which responds to all the methods that the corresponding Class does.
|
7
|
+
# Some might be structural or dependent or generic! The key part is that
|
8
|
+
# method signatures can be listed and queried.
|
9
|
+
module Protocols
|
10
|
+
# The base class for all protocols.
|
11
|
+
class Base
|
12
|
+
include Comparable
|
13
|
+
|
14
|
+
# Returns the list of all known signatures that this protocol responds
|
15
|
+
# to.
|
16
|
+
#
|
17
|
+
# @return [Array<Signature>] the supported signatures for this protocol.
|
18
|
+
def signatures
|
19
|
+
raise NotImplementedError.new('You must implement #signatures yourself.')
|
20
|
+
end
|
21
|
+
|
22
|
+
# Compares the protocol to another protocol by comparing their signatures
|
23
|
+
# list (sorted).
|
24
|
+
def <=>(other)
|
25
|
+
self.signatures.sort <=> other.signatures.sort
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# This is a simple protocol whose signature set is just the union of the
|
30
|
+
# signature sets of its constituent protocols.
|
31
|
+
class UnionProtocol < Base
|
32
|
+
# Initializes the Union protocol to a set of constituent protocols.
|
33
|
+
#
|
34
|
+
# @param [Array<Base>] constituents the set of constituent protocols that
|
35
|
+
# this protocol is a union of.
|
36
|
+
def initialize(constituents)
|
37
|
+
@protocols = constituents
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the list of all known signatures that this protocol responds
|
41
|
+
# to, which is the union of all the constituent protocols.
|
42
|
+
#
|
43
|
+
# @return [Array<Signature>] the supported signatures for this protocol.
|
44
|
+
def signatures
|
45
|
+
@protocols.map(&:signatures).inject(:|)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# A protocol that has the same signatures as a given class.
|
50
|
+
class ClassProtocol < Base
|
51
|
+
attr_reader :class_used
|
52
|
+
|
53
|
+
# Initializes the class protocol with the given class.
|
54
|
+
#
|
55
|
+
# @param [WoolClass] klass the wool class whose protocol we are representing
|
56
|
+
def initialize(klass)
|
57
|
+
@class_used = klass
|
58
|
+
end
|
59
|
+
|
60
|
+
# Compares two ClassProtocols. Has a short-circuit check if two protocols
|
61
|
+
# refer to the same class or not. Otherwise, it has to check the signatures.
|
62
|
+
#
|
63
|
+
# @param [Protocols::Base] other the other protocol to compare against
|
64
|
+
# @return [-1, 0, 1] a standard comparison result.
|
65
|
+
def <=>(other)
|
66
|
+
if ClassProtocol === other && self.class_used == other.class_used
|
67
|
+
then return 0
|
68
|
+
else self.signatures <=> other.signatures
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns all the signatures that the class responds to, since this protocol
|
73
|
+
# has the same signatures.
|
74
|
+
#
|
75
|
+
# @return [Array<Signature>] the supported signatures for this protocol.
|
76
|
+
def signatures
|
77
|
+
@class_used.signatures
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Wool
|
2
|
+
module SexpAnalysis
|
3
|
+
# This class models a scope in Ruby. It has a constant table,
|
4
|
+
# a self pointer, and a parent pointer to the enclosing scope.
|
5
|
+
# It also has a local variable table.
|
6
|
+
class Scope
|
7
|
+
attr_reader :constants, :self_ptr, :parent
|
8
|
+
def initialize(parent, self_ptr, constants={})
|
9
|
+
@parent, @self_ptr, @constants = parent, self_ptr, constants
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Wool
|
2
|
+
# This is a set of methods that get provided to Warnings so they can perform
|
3
|
+
# parse-tree analysis of their bodies.
|
4
|
+
module SexpAnalysis
|
5
|
+
extend ModuleExtensions
|
6
|
+
|
7
|
+
# Replaces the ParseTree Sexps by adding a few handy-dandy methods.
|
8
|
+
class Sexp < Array
|
9
|
+
extend ModuleExtensions
|
10
|
+
|
11
|
+
# Returns a mutable reference to the list of annotations that will run
|
12
|
+
# upon initializing a new Sexp.
|
13
|
+
#
|
14
|
+
# @return [Array[Class < Annotation]] the activated annotations, in the
|
15
|
+
# order they will run in.
|
16
|
+
cattr_accessor_with_default :annotations, []
|
17
|
+
|
18
|
+
# Initializes the Sexp with the contents of the array returned by Ripper.
|
19
|
+
#
|
20
|
+
# @param [Array<Object>] other the other
|
21
|
+
def initialize(other)
|
22
|
+
replace other
|
23
|
+
replace_children!
|
24
|
+
self.class.annotations.each {|annotator| annotator.annotate!(self) }
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Array<Object>] the children of the node.
|
28
|
+
def children
|
29
|
+
(Array === self[0] ? self : self[1..-1]) || []
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Symbol] the type of the node.
|
33
|
+
def type
|
34
|
+
self[0]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Replaces the children with Sexp versions of them
|
38
|
+
def replace_children!
|
39
|
+
replace(map do |x|
|
40
|
+
case x
|
41
|
+
when Array
|
42
|
+
self.class.new(x)
|
43
|
+
else x
|
44
|
+
end
|
45
|
+
end)
|
46
|
+
end
|
47
|
+
private :replace_children!
|
48
|
+
|
49
|
+
# Evaluates the constant reference/path with the given scope
|
50
|
+
# as context.
|
51
|
+
def eval_as_constant(scope)
|
52
|
+
case type
|
53
|
+
# [:var_ref, [:@const, "B", [1, 17]]]
|
54
|
+
when :var_ref then scope.constants[self[1][1]]
|
55
|
+
# [:const_ref, [:@const, "M", [1, 17]]]
|
56
|
+
when :const_ref then scope.constants[self[1][1]]
|
57
|
+
# [:top_const_ref, [:@const, "M", [1, 2]]]
|
58
|
+
when :top_const_ref then Scope::GlobalScope.constants[self[1][1]]
|
59
|
+
# [:@const, "B", [1, 7]]
|
60
|
+
when :@const then scope.constants[self[1]]
|
61
|
+
# [:const_path_ref, [:var_ref, [:@const, "B", [1, 17]]], [:@const, "M", [1, 20]]]
|
62
|
+
when :const_path_ref
|
63
|
+
left, right = children
|
64
|
+
scope = left.eval_as_constant(scope).scope
|
65
|
+
right.eval_as_constant(scope)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Global annotations are only run once, at the root.
|
71
|
+
cattr_accessor_with_default :global_annotations, []
|
72
|
+
|
73
|
+
# Parses the given text.
|
74
|
+
#
|
75
|
+
# @param [String] body (self.body) The text to parse
|
76
|
+
# @return [Sexp, NilClass] the sexp representing the input text.
|
77
|
+
def parse(body = self.body)
|
78
|
+
result = Sexp.new Ripper.sexp(body)
|
79
|
+
SexpAnalysis.global_annotations.each {|annotator| annotator.annotate!(result)}
|
80
|
+
result
|
81
|
+
end
|
82
|
+
|
83
|
+
# Finds all sexps of the given type in the given Sexp tree.
|
84
|
+
#
|
85
|
+
# @param [Symbol] type the type of sexp to search for
|
86
|
+
# @param [Sexp] tree (self.parse(self.body)) The tree to search in. Leave
|
87
|
+
# blank to search the entire body.
|
88
|
+
# @return [Array<Sexp>] all sexps in the input tree (or whole body) that
|
89
|
+
# are of the given type.
|
90
|
+
def find_sexps(type, tree = self.parse(self.body))
|
91
|
+
result = tree[0] == type ? [tree] : []
|
92
|
+
tree.each do |node|
|
93
|
+
result.concat find_sexps(type, node) if node.is_a?(Array)
|
94
|
+
end
|
95
|
+
result
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|