ruby-lint 0.0.1a
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rbenv-version +1 -0
- data/.yardopts +10 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/MANIFEST +79 -0
- data/README.md +48 -0
- data/Rakefile +14 -0
- data/bin/rlint +6 -0
- data/doc/.gitkeep +0 -0
- data/doc/build/.gitkeep +0 -0
- data/doc/css/.gitkeep +0 -0
- data/doc/css/common.css +68 -0
- data/lib/rlint/analyze/coding_style.rb +407 -0
- data/lib/rlint/analyze/definitions.rb +244 -0
- data/lib/rlint/analyze/method_validation.rb +104 -0
- data/lib/rlint/analyze/shadowing_variables.rb +37 -0
- data/lib/rlint/analyze/undefined_variables.rb +99 -0
- data/lib/rlint/analyze/unused_variables.rb +103 -0
- data/lib/rlint/callback.rb +67 -0
- data/lib/rlint/cli.rb +167 -0
- data/lib/rlint/constant_importer.rb +102 -0
- data/lib/rlint/definition.rb +230 -0
- data/lib/rlint/formatter/text.rb +54 -0
- data/lib/rlint/helper/definition_resolver.rb +143 -0
- data/lib/rlint/helper/scoping.rb +138 -0
- data/lib/rlint/iterator.rb +193 -0
- data/lib/rlint/options.rb +58 -0
- data/lib/rlint/parser.rb +1252 -0
- data/lib/rlint/parser_error.rb +42 -0
- data/lib/rlint/report.rb +98 -0
- data/lib/rlint/token/assignment_token.rb +46 -0
- data/lib/rlint/token/begin_rescue_token.rb +57 -0
- data/lib/rlint/token/block_token.rb +17 -0
- data/lib/rlint/token/case_token.rb +44 -0
- data/lib/rlint/token/class_token.rb +24 -0
- data/lib/rlint/token/method_definition_token.rb +64 -0
- data/lib/rlint/token/method_token.rb +58 -0
- data/lib/rlint/token/parameters_token.rb +99 -0
- data/lib/rlint/token/regexp_token.rb +15 -0
- data/lib/rlint/token/statement_token.rb +69 -0
- data/lib/rlint/token/token.rb +162 -0
- data/lib/rlint/token/variable_token.rb +18 -0
- data/lib/rlint/version.rb +3 -0
- data/lib/rlint.rb +36 -0
- data/ruby-lint.gemspec +23 -0
- data/spec/benchmarks/memory.rb +52 -0
- data/spec/benchmarks/parse_parser.rb +16 -0
- data/spec/helper.rb +4 -0
- data/spec/rlint/analyze/coding_style.rb +224 -0
- data/spec/rlint/analyze/definitions/classes.rb +114 -0
- data/spec/rlint/analyze/definitions/methods.rb +91 -0
- data/spec/rlint/analyze/definitions/modules.rb +207 -0
- data/spec/rlint/analyze/definitions/variables.rb +103 -0
- data/spec/rlint/analyze/method_validation.rb +177 -0
- data/spec/rlint/analyze/shadowing_variables.rb +30 -0
- data/spec/rlint/analyze/undefined_variables.rb +230 -0
- data/spec/rlint/analyze/unused_variables.rb +225 -0
- data/spec/rlint/callback.rb +28 -0
- data/spec/rlint/constant_importer.rb +27 -0
- data/spec/rlint/definition.rb +96 -0
- data/spec/rlint/formatter/text.rb +21 -0
- data/spec/rlint/iterator.rb +452 -0
- data/spec/rlint/parser/arrays.rb +147 -0
- data/spec/rlint/parser/classes.rb +152 -0
- data/spec/rlint/parser/errors.rb +19 -0
- data/spec/rlint/parser/hashes.rb +136 -0
- data/spec/rlint/parser/methods.rb +249 -0
- data/spec/rlint/parser/modules.rb +49 -0
- data/spec/rlint/parser/objects.rb +39 -0
- data/spec/rlint/parser/operators.rb +75 -0
- data/spec/rlint/parser/procs.rb +113 -0
- data/spec/rlint/parser/ranges.rb +49 -0
- data/spec/rlint/parser/regexp.rb +31 -0
- data/spec/rlint/parser/scalars.rb +93 -0
- data/spec/rlint/parser/statements.rb +550 -0
- data/spec/rlint/parser/variables.rb +181 -0
- data/spec/rlint/report.rb +30 -0
- data/task/test.rake +6 -0
- metadata +188 -0
@@ -0,0 +1,244 @@
|
|
1
|
+
module Rlint
|
2
|
+
module Analyze
|
3
|
+
##
|
4
|
+
# {Rlint::Analyze::Definitions} is a callback class that is used for
|
5
|
+
# building a list of all the definitions (variables, methods, etc) of a
|
6
|
+
# block of Ruby code.
|
7
|
+
#
|
8
|
+
# The resulting instance of {Rlint::Definition} is stored in the `@storage`
|
9
|
+
# instance variable under the key `:scope`. This makes it possible for
|
10
|
+
# other callback classes to access this data easily.
|
11
|
+
#
|
12
|
+
class Definitions < Rlint::Callback
|
13
|
+
include Helper::Scoping
|
14
|
+
|
15
|
+
##
|
16
|
+
# A short description of this class.
|
17
|
+
#
|
18
|
+
# @return [String]
|
19
|
+
#
|
20
|
+
DESCRIPTION = 'Builds a list of definitions, always enabled.'
|
21
|
+
|
22
|
+
##
|
23
|
+
# Array containing the key names of the variables that should be exported
|
24
|
+
# out of a method definition.
|
25
|
+
#
|
26
|
+
# @return [Array]
|
27
|
+
#
|
28
|
+
EXPORT_VARIABLES = [:instance_variable, :class_variable, :constant]
|
29
|
+
|
30
|
+
##
|
31
|
+
# Hash containing the scoping data to copy over when extending a class
|
32
|
+
# using a module.
|
33
|
+
#
|
34
|
+
# @return [Hash]
|
35
|
+
#
|
36
|
+
INCLUDE_SYMBOLS = {
|
37
|
+
'include' => {
|
38
|
+
:constant => :constant,
|
39
|
+
:instance_method => :instance_method
|
40
|
+
},
|
41
|
+
'extend' => {
|
42
|
+
:constant => :constant,
|
43
|
+
:instance_method => :method
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
##
|
48
|
+
# Called when a value is assigned to a variable.
|
49
|
+
#
|
50
|
+
# @param [Rlint::Token::AssignmentToken] token
|
51
|
+
#
|
52
|
+
def on_assignment(token)
|
53
|
+
if token.type == :global_variable
|
54
|
+
variable_scope = @storage[:scope]
|
55
|
+
else
|
56
|
+
variable_scope = scope
|
57
|
+
end
|
58
|
+
|
59
|
+
# Assignment using a constant path. In this case each path segment
|
60
|
+
# should exist (with the exception of the last one) for the assignment
|
61
|
+
# to take place.
|
62
|
+
if token.name.is_a?(Array)
|
63
|
+
name_scope = scope
|
64
|
+
|
65
|
+
token.name[0..-2].each do |segment|
|
66
|
+
name_scope = name_scope.lookup(:constant, segment)
|
67
|
+
end
|
68
|
+
|
69
|
+
if name_scope
|
70
|
+
variable_scope = name_scope
|
71
|
+
else
|
72
|
+
return
|
73
|
+
end
|
74
|
+
|
75
|
+
name = token.name[-1]
|
76
|
+
type = :constant
|
77
|
+
else
|
78
|
+
name = token.name
|
79
|
+
type = token.type
|
80
|
+
end
|
81
|
+
|
82
|
+
variable_scope.add(
|
83
|
+
type,
|
84
|
+
name,
|
85
|
+
Definition.new(nil, :token => token, :reset => false)
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Called when a new method is defined.
|
91
|
+
#
|
92
|
+
# @param [Rlint::Token::MethodDefinitionToken] token
|
93
|
+
#
|
94
|
+
def on_method_definition(token)
|
95
|
+
type = :instance_method
|
96
|
+
new_scope = Definition.new(scope, :token => token)
|
97
|
+
target = scope
|
98
|
+
|
99
|
+
token.parameters.each do |param|
|
100
|
+
new_scope.add(
|
101
|
+
param.type,
|
102
|
+
param.name,
|
103
|
+
Definition.new(nil, :token => param, :reset => false)
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
# The method is a class method.
|
108
|
+
if token.receiver
|
109
|
+
type = :method
|
110
|
+
|
111
|
+
if token.receiver.name != 'self' \
|
112
|
+
and token.receiver.name != @namespace[-1]
|
113
|
+
found = target.lookup(token.receiver.type, token.receiver.name)
|
114
|
+
target = found if found
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
target.add(type, token.name, new_scope)
|
119
|
+
|
120
|
+
@scopes << new_scope
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Called after a method definition has been processed.
|
125
|
+
#
|
126
|
+
# @see Rlint::Analyze::Definitions#on_method_definition
|
127
|
+
#
|
128
|
+
def after_method_definition(token)
|
129
|
+
# TODO: exporting these variables should only be done if the method is
|
130
|
+
# actually called.
|
131
|
+
last_scope = @scopes.pop
|
132
|
+
|
133
|
+
EXPORT_VARIABLES.each do |key|
|
134
|
+
scope.symbols[key] = scope.symbols[key].merge(
|
135
|
+
last_scope.symbols[key]
|
136
|
+
)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
##
|
141
|
+
# Called when a class definition is found.
|
142
|
+
#
|
143
|
+
# @param [Rlint::Token::ClassToken] token
|
144
|
+
#
|
145
|
+
def on_class(token)
|
146
|
+
name = token.name.join('::')
|
147
|
+
@namespace << name
|
148
|
+
existing = scope.lookup(:constant, name)
|
149
|
+
|
150
|
+
# If a class has already been defined the scope should not be
|
151
|
+
# overwritten.
|
152
|
+
if existing
|
153
|
+
existing.parent << scope
|
154
|
+
@scopes << existing
|
155
|
+
|
156
|
+
return
|
157
|
+
end
|
158
|
+
|
159
|
+
parent = scope.lookup(:constant, token.parent.join('::'))
|
160
|
+
new_scope = Definition.new([parent, scope], :token => token)
|
161
|
+
|
162
|
+
scope.add(:constant, name, new_scope)
|
163
|
+
|
164
|
+
@scopes << new_scope
|
165
|
+
end
|
166
|
+
|
167
|
+
##
|
168
|
+
# Called after a class definition was found and processed.
|
169
|
+
#
|
170
|
+
# @see Rlint::Analyze::Definitions#on_class
|
171
|
+
#
|
172
|
+
def after_class(token)
|
173
|
+
@scopes.pop
|
174
|
+
@namespace.pop
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Called when a module is defined.
|
179
|
+
#
|
180
|
+
# @param [Rlint::Token::Token] token
|
181
|
+
#
|
182
|
+
def on_module(token)
|
183
|
+
name = token.name.join('::')
|
184
|
+
@namespace << name
|
185
|
+
existing = scope.lookup(:constant, name)
|
186
|
+
|
187
|
+
# If a module has already been defined the scope should not be
|
188
|
+
# overwritten.
|
189
|
+
if existing
|
190
|
+
existing.parent << scope
|
191
|
+
@scopes << existing
|
192
|
+
|
193
|
+
return
|
194
|
+
end
|
195
|
+
|
196
|
+
new_scope = Definition.new(scope, :token => token)
|
197
|
+
|
198
|
+
scope.add(:constant, name, new_scope)
|
199
|
+
|
200
|
+
@scopes << new_scope
|
201
|
+
end
|
202
|
+
|
203
|
+
##
|
204
|
+
# Called after a module definition has been processed.
|
205
|
+
#
|
206
|
+
# @see Rlint::Analyze::Definitions#on_module
|
207
|
+
#
|
208
|
+
def after_module(token)
|
209
|
+
@scopes.pop
|
210
|
+
@namespace.pop
|
211
|
+
end
|
212
|
+
|
213
|
+
##
|
214
|
+
# Called when a method call is found. This callback is used to extend
|
215
|
+
# classes using modules.
|
216
|
+
#
|
217
|
+
# @param [Rlint::Token::MethodToken] token
|
218
|
+
#
|
219
|
+
def on_method(token)
|
220
|
+
if INCLUDE_SYMBOLS.key?(token.name)
|
221
|
+
token.parameters.each do |param|
|
222
|
+
found = nil
|
223
|
+
|
224
|
+
# Extract the definition and scope to include.
|
225
|
+
if param.type == :constant_path
|
226
|
+
found = resolve_definition(param.name)
|
227
|
+
elsif param.type == :constant
|
228
|
+
found = scope.lookup(:constant, param.name)
|
229
|
+
end
|
230
|
+
|
231
|
+
next unless found
|
232
|
+
|
233
|
+
# Copy over all the constants and methods.
|
234
|
+
INCLUDE_SYMBOLS[token.name].each do |source, target|
|
235
|
+
found.symbols[source].each do |name, data|
|
236
|
+
scope.add(target, name, data)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end # Definitions
|
243
|
+
end # Analyze
|
244
|
+
end # Rlint
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Rlint
|
2
|
+
module Analyze
|
3
|
+
##
|
4
|
+
# {Rlint::Analyze::MethodValidation} is used to validate method calls.
|
5
|
+
# Errors are added for calling undefined methods, calling methods with
|
6
|
+
# invalid parameters and so on.
|
7
|
+
#
|
8
|
+
class MethodValidation < Rlint::Callback
|
9
|
+
include Helper::DefinitionResolver
|
10
|
+
|
11
|
+
##
|
12
|
+
# A short description of this class.
|
13
|
+
#
|
14
|
+
# @return [String]
|
15
|
+
#
|
16
|
+
DESCRIPTION = 'Validates method calls and the specified parameters.'
|
17
|
+
|
18
|
+
##
|
19
|
+
# Hash containing the various Ruby classes that are used to represent
|
20
|
+
# various types.
|
21
|
+
#
|
22
|
+
# @return [Hash]
|
23
|
+
#
|
24
|
+
TYPE_CLASSES = {
|
25
|
+
:string => 'String',
|
26
|
+
:integer => 'Fixnum', # Fixnum and Bignum share the same methods.
|
27
|
+
:float => 'Float',
|
28
|
+
:symbol => 'Symbol',
|
29
|
+
:array => 'Array',
|
30
|
+
:hash => 'Hash',
|
31
|
+
:brace_block => 'Proc',
|
32
|
+
:lambda => 'Proc',
|
33
|
+
:regexp => 'Regexp',
|
34
|
+
:range => 'Range'
|
35
|
+
}
|
36
|
+
|
37
|
+
##
|
38
|
+
# Called when a method call is found.
|
39
|
+
#
|
40
|
+
# @param [Rlint::Token::MethodToken] token
|
41
|
+
#
|
42
|
+
def on_method(token)
|
43
|
+
# Method called on a receiver (e.g. `String.new`).
|
44
|
+
if token.receiver
|
45
|
+
receiver_name = token.receiver.name
|
46
|
+
receiver_scope = scope
|
47
|
+
receiver_type = token.receiver.type
|
48
|
+
method_type = :instance_method
|
49
|
+
|
50
|
+
if receiver_name.is_a?(Array)
|
51
|
+
return unless valid_constant_path?(token.receiver)
|
52
|
+
|
53
|
+
receiver_scope = resolve_definition(receiver_name)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Method calls on variables such as `name.upcase`.
|
57
|
+
if token.receiver.is_a?(Token::VariableToken) \
|
58
|
+
and receiver_type != :constant \
|
59
|
+
and receiver_type != :constant_path
|
60
|
+
value = receiver_scope.lookup(receiver_type, receiver_name)
|
61
|
+
|
62
|
+
if !value.nil? and !value.token.value.nil?
|
63
|
+
value = value.token.value
|
64
|
+
receiver_type = TYPE_CLASSES[value.type]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Extract the class from a method call.
|
68
|
+
if value.respond_to?(:receiver)
|
69
|
+
while value.respond_to?(:receiver)
|
70
|
+
value = value.receiver
|
71
|
+
end
|
72
|
+
|
73
|
+
receiver_type = value.name
|
74
|
+
end
|
75
|
+
|
76
|
+
# Methods called directly on a type such as `'name'.upcase`.
|
77
|
+
elsif TYPE_CLASSES[receiver_type]
|
78
|
+
receiver_type = TYPE_CLASSES[receiver_type]
|
79
|
+
|
80
|
+
# Everything else.
|
81
|
+
else
|
82
|
+
method_type = :method
|
83
|
+
receiver_type = receiver_name.is_a?(Array) \
|
84
|
+
? receiver_name[-1] \
|
85
|
+
: receiver_name
|
86
|
+
end
|
87
|
+
|
88
|
+
# Retrieve the constant to check for the existence of the method.
|
89
|
+
found = receiver_scope.lookup(:constant, receiver_type)
|
90
|
+
|
91
|
+
if found and !definition_exists?(method_type, token, found)
|
92
|
+
if method_type == :instance_method
|
93
|
+
error = "undefined instance method #{token.name}"
|
94
|
+
else
|
95
|
+
error = "undefined class method #{token.name}"
|
96
|
+
end
|
97
|
+
|
98
|
+
error(error, token.line, token.column)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end # MethodValidation
|
103
|
+
end # Analyze
|
104
|
+
end # Rlint
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Rlint
|
2
|
+
module Analyze
|
3
|
+
##
|
4
|
+
# {Rlint::Analyze::ShadowingVariables} is used to add warnings when block
|
5
|
+
# parameters shadow outer local variables.
|
6
|
+
#
|
7
|
+
class ShadowingVariables < Rlint::Callback
|
8
|
+
include Helper::DefinitionResolver
|
9
|
+
|
10
|
+
##
|
11
|
+
# A short description of this class.
|
12
|
+
#
|
13
|
+
# @return [String]
|
14
|
+
#
|
15
|
+
DESCRIPTION = 'Checks for variables that shadow other variables.'
|
16
|
+
|
17
|
+
##
|
18
|
+
# Called when a block is found. This callback is used to check if the
|
19
|
+
# parameters of the block shadow existing local variables defined in the
|
20
|
+
# outer scope.
|
21
|
+
#
|
22
|
+
# @param [Rlint::Token::BlockToken] token The token of the block.
|
23
|
+
#
|
24
|
+
def on_block(token)
|
25
|
+
token.parameters.each do |param|
|
26
|
+
if scope.lookup(param.type, param.name)
|
27
|
+
warning(
|
28
|
+
"shadowing outer local variable #{param.name}",
|
29
|
+
param.line,
|
30
|
+
param.column
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end # ShadowingVariables
|
36
|
+
end # Analyze
|
37
|
+
end # Rlint
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Rlint
|
2
|
+
module Analyze
|
3
|
+
##
|
4
|
+
# {Rlint::Analyze::UndefinedVariables} is used to add errors for the use of
|
5
|
+
# undefined variables.
|
6
|
+
#
|
7
|
+
class UndefinedVariables < Rlint::Callback
|
8
|
+
include Helper::DefinitionResolver
|
9
|
+
|
10
|
+
##
|
11
|
+
# A short description of this class.
|
12
|
+
#
|
13
|
+
# @return [String]
|
14
|
+
#
|
15
|
+
DESCRIPTION = 'Checks for the use of undefined variables.'
|
16
|
+
|
17
|
+
[
|
18
|
+
'instance_variable',
|
19
|
+
'class_variable',
|
20
|
+
'global_variable',
|
21
|
+
'constant'
|
22
|
+
].each do |name|
|
23
|
+
readable = name.gsub('_', ' ')
|
24
|
+
|
25
|
+
define_method('on_' + name) do |token|
|
26
|
+
unless definition_exists?(token.type, token)
|
27
|
+
error(
|
28
|
+
"undefined #{readable} #{token.name}",
|
29
|
+
token.line,
|
30
|
+
token.column
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Called when a variable assignment is found. Used to validate constant
|
38
|
+
# paths before assigning data to them.
|
39
|
+
#
|
40
|
+
# @param [Rlint::Token::AssignmentToken] token
|
41
|
+
#
|
42
|
+
def on_assignment(token)
|
43
|
+
on_constant_path(token) if token.name.is_a?(Array)
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Called when a constant path is found.
|
48
|
+
#
|
49
|
+
# @param [Rlint::Token::VariableToken] token
|
50
|
+
#
|
51
|
+
def on_constant_path(token)
|
52
|
+
current = scope
|
53
|
+
segments = []
|
54
|
+
|
55
|
+
token.name.each do |segment|
|
56
|
+
segments << segment
|
57
|
+
found = current.lookup(:constant, segment)
|
58
|
+
|
59
|
+
if found and found.token.line < token.line
|
60
|
+
current = found
|
61
|
+
else
|
62
|
+
error(
|
63
|
+
"undefined constant #{segments.join('::')}",
|
64
|
+
token.line,
|
65
|
+
token.column
|
66
|
+
)
|
67
|
+
|
68
|
+
return
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Called when a method call is found.
|
75
|
+
#
|
76
|
+
# @param [Rlint::Token::MethodToken] token
|
77
|
+
#
|
78
|
+
def on_method(token)
|
79
|
+
kernel_method = false
|
80
|
+
kernel = scope.lookup(:constant, 'Kernel')
|
81
|
+
|
82
|
+
if kernel.lookup(:method, token.name) \
|
83
|
+
or kernel.lookup(:instance_method, token.name)
|
84
|
+
kernel_method = true
|
85
|
+
end
|
86
|
+
|
87
|
+
if !token.receiver \
|
88
|
+
and !kernel_method \
|
89
|
+
and !definition_exists?(:instance_method, token)
|
90
|
+
error(
|
91
|
+
"undefined local variable or method #{token.name}",
|
92
|
+
token.line,
|
93
|
+
token.column
|
94
|
+
)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end # UndefinedVariables
|
98
|
+
end # Analyze
|
99
|
+
end # Rlint
|