ruby-lint 0.0.1a

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/.gitignore +5 -0
  2. data/.rbenv-version +1 -0
  3. data/.yardopts +10 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +19 -0
  6. data/MANIFEST +79 -0
  7. data/README.md +48 -0
  8. data/Rakefile +14 -0
  9. data/bin/rlint +6 -0
  10. data/doc/.gitkeep +0 -0
  11. data/doc/build/.gitkeep +0 -0
  12. data/doc/css/.gitkeep +0 -0
  13. data/doc/css/common.css +68 -0
  14. data/lib/rlint/analyze/coding_style.rb +407 -0
  15. data/lib/rlint/analyze/definitions.rb +244 -0
  16. data/lib/rlint/analyze/method_validation.rb +104 -0
  17. data/lib/rlint/analyze/shadowing_variables.rb +37 -0
  18. data/lib/rlint/analyze/undefined_variables.rb +99 -0
  19. data/lib/rlint/analyze/unused_variables.rb +103 -0
  20. data/lib/rlint/callback.rb +67 -0
  21. data/lib/rlint/cli.rb +167 -0
  22. data/lib/rlint/constant_importer.rb +102 -0
  23. data/lib/rlint/definition.rb +230 -0
  24. data/lib/rlint/formatter/text.rb +54 -0
  25. data/lib/rlint/helper/definition_resolver.rb +143 -0
  26. data/lib/rlint/helper/scoping.rb +138 -0
  27. data/lib/rlint/iterator.rb +193 -0
  28. data/lib/rlint/options.rb +58 -0
  29. data/lib/rlint/parser.rb +1252 -0
  30. data/lib/rlint/parser_error.rb +42 -0
  31. data/lib/rlint/report.rb +98 -0
  32. data/lib/rlint/token/assignment_token.rb +46 -0
  33. data/lib/rlint/token/begin_rescue_token.rb +57 -0
  34. data/lib/rlint/token/block_token.rb +17 -0
  35. data/lib/rlint/token/case_token.rb +44 -0
  36. data/lib/rlint/token/class_token.rb +24 -0
  37. data/lib/rlint/token/method_definition_token.rb +64 -0
  38. data/lib/rlint/token/method_token.rb +58 -0
  39. data/lib/rlint/token/parameters_token.rb +99 -0
  40. data/lib/rlint/token/regexp_token.rb +15 -0
  41. data/lib/rlint/token/statement_token.rb +69 -0
  42. data/lib/rlint/token/token.rb +162 -0
  43. data/lib/rlint/token/variable_token.rb +18 -0
  44. data/lib/rlint/version.rb +3 -0
  45. data/lib/rlint.rb +36 -0
  46. data/ruby-lint.gemspec +23 -0
  47. data/spec/benchmarks/memory.rb +52 -0
  48. data/spec/benchmarks/parse_parser.rb +16 -0
  49. data/spec/helper.rb +4 -0
  50. data/spec/rlint/analyze/coding_style.rb +224 -0
  51. data/spec/rlint/analyze/definitions/classes.rb +114 -0
  52. data/spec/rlint/analyze/definitions/methods.rb +91 -0
  53. data/spec/rlint/analyze/definitions/modules.rb +207 -0
  54. data/spec/rlint/analyze/definitions/variables.rb +103 -0
  55. data/spec/rlint/analyze/method_validation.rb +177 -0
  56. data/spec/rlint/analyze/shadowing_variables.rb +30 -0
  57. data/spec/rlint/analyze/undefined_variables.rb +230 -0
  58. data/spec/rlint/analyze/unused_variables.rb +225 -0
  59. data/spec/rlint/callback.rb +28 -0
  60. data/spec/rlint/constant_importer.rb +27 -0
  61. data/spec/rlint/definition.rb +96 -0
  62. data/spec/rlint/formatter/text.rb +21 -0
  63. data/spec/rlint/iterator.rb +452 -0
  64. data/spec/rlint/parser/arrays.rb +147 -0
  65. data/spec/rlint/parser/classes.rb +152 -0
  66. data/spec/rlint/parser/errors.rb +19 -0
  67. data/spec/rlint/parser/hashes.rb +136 -0
  68. data/spec/rlint/parser/methods.rb +249 -0
  69. data/spec/rlint/parser/modules.rb +49 -0
  70. data/spec/rlint/parser/objects.rb +39 -0
  71. data/spec/rlint/parser/operators.rb +75 -0
  72. data/spec/rlint/parser/procs.rb +113 -0
  73. data/spec/rlint/parser/ranges.rb +49 -0
  74. data/spec/rlint/parser/regexp.rb +31 -0
  75. data/spec/rlint/parser/scalars.rb +93 -0
  76. data/spec/rlint/parser/statements.rb +550 -0
  77. data/spec/rlint/parser/variables.rb +181 -0
  78. data/spec/rlint/report.rb +30 -0
  79. data/task/test.rake +6 -0
  80. 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