ruby-lint 0.0.1a
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/.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
|