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,54 @@
1
+ module Rlint
2
+ module Formatter
3
+ ##
4
+ # {Rlint::Formatter::Text} is a formatter class that formats a report in a
5
+ # format similar to the one used by Ruby when validating a Ruby file using
6
+ # the `ruby` executable. An example of this format is the following:
7
+ #
8
+ # b.rb: error: line 1, column 1: test error
9
+ # b.rb: info: line 3, column 1: test info b.rb
10
+ #
11
+ # Basic usage of this formatter is as following:
12
+ #
13
+ # report = Rlint::Report.new
14
+ # formatter = Rlint::Formatter::Text.new
15
+ #
16
+ # # Add some data to the report.
17
+ # # ...
18
+ #
19
+ # puts formatter.format(report)
20
+ #
21
+ class Text
22
+ ##
23
+ # A short description of the class.
24
+ #
25
+ # @return [String]
26
+ #
27
+ DESCRIPTION = 'Formats a report in a human readable format.'
28
+
29
+ ##
30
+ # Formats the specified report.
31
+ #
32
+ # @param [Rlint::Report] report The report to format.
33
+ # @return [String]
34
+ #
35
+ def format(report)
36
+ lines = []
37
+
38
+ report.messages.sort.each do |level, messages|
39
+ messages.each do |message|
40
+ lines << '%s: %s: line %s, column %s: %s' % [
41
+ report.file,
42
+ level,
43
+ message[:line],
44
+ message[:column],
45
+ message[:message]
46
+ ]
47
+ end
48
+ end
49
+
50
+ return lines.join("\n")
51
+ end
52
+ end # Text
53
+ end # Formatter
54
+ end # Rlint
@@ -0,0 +1,143 @@
1
+ module Rlint
2
+ module Helper
3
+ ##
4
+ # {Rlint::Helper::DefinitionResolver} is a helper module that can be used
5
+ # to work with scoping information similar to {Rlint::Helper::Scoping}.
6
+ #
7
+ # This module depends on {Rlint::Helper::Scoping} and will include it
8
+ # automatically.
9
+ #
10
+ # ## Methods
11
+ #
12
+ # This module defines a set of methods that are called before and after a
13
+ # method, class or module is defined. These methods take care of retrieving
14
+ # the scope for each definition.
15
+ #
16
+ # These methods will also call two special callback methods that make it
17
+ # easier to run code whenever the scope changes:
18
+ #
19
+ # * `on_new_scope`: called when a new scope has been set.
20
+ # * `after_new_scope`: called when the code has reached the end of the
21
+ # current scope.
22
+ #
23
+ # Using these methods classes including this module don't have to redefine
24
+ # methods such as `on_class` (unless explicitly needed of course). Both of
25
+ # these methods are called *after* the current scope has been updated.
26
+ #
27
+ module DefinitionResolver
28
+ include Scoping
29
+
30
+ ##
31
+ # Called before processing all the tokens.
32
+ #
33
+ def on_start
34
+ call_method(:on_new_scope)
35
+ end
36
+
37
+ ##
38
+ # Called after all the tokens have been processed.
39
+ #
40
+ def on_finish
41
+ call_method(:after_new_scope)
42
+ end
43
+
44
+ ##
45
+ # Sets the scope for the current method definition.
46
+ #
47
+ # @param [Rlint::Token::MethodDefinitionToken] token
48
+ #
49
+ def on_method_definition(token)
50
+ @scopes << scope.lookup(
51
+ token.receiver ? :method : :instance_method,
52
+ token.name
53
+ )
54
+
55
+ @call_types << :instance_method
56
+
57
+ call_method(:on_new_scope)
58
+ end
59
+
60
+ ##
61
+ # Resets the scope back to the one used before the method definition.
62
+ #
63
+ # @see Rlint::Helper::DefinitionResolver#on_method_definition
64
+ #
65
+ def after_method_definition(token)
66
+ @scopes.pop
67
+ @call_types.pop
68
+
69
+ call_method(:after_new_scope)
70
+ end
71
+
72
+ ##
73
+ # Sets the scope for the current class.
74
+ #
75
+ # @param [Rlint::Token::ClassToken] token
76
+ #
77
+ def on_class(token)
78
+ name = token.name.join('::')
79
+
80
+ @scopes << scope.lookup(:constant, name)
81
+ @namespace << name
82
+ @call_types << :method
83
+
84
+ call_method(:on_new_scope)
85
+ end
86
+
87
+ ##
88
+ # Resets the scope back to the one used before the class definition.
89
+ #
90
+ # @see Rlint::Helper::DefinitionResolver#on_class
91
+ #
92
+ def after_class(token)
93
+ @scopes.pop
94
+ @namespace.pop
95
+ @call_types.pop
96
+
97
+ call_method(:after_new_scope)
98
+ end
99
+
100
+ ##
101
+ # Sets the scope for the current module.
102
+ #
103
+ # @param [Rlint::Token::Token] token
104
+ #
105
+ def on_module(token)
106
+ name = token.name.join('::')
107
+
108
+ @scopes << scope.lookup(:constant, name)
109
+ @namespace << name
110
+ @call_types << :method
111
+
112
+ call_method(:on_new_scope)
113
+ end
114
+
115
+ ##
116
+ # Resets the scope back to the one used before the module definition.
117
+ #
118
+ # @see Rlint::Helper::DefinitionResolver#on_module
119
+ #
120
+ def after_module(token)
121
+ @scopes.pop
122
+ @namespace.pop
123
+ @call_types.pop
124
+
125
+ call_method(:after_new_scope)
126
+ end
127
+
128
+ private
129
+
130
+ ##
131
+ # Calls the specified method if it exists.
132
+ #
133
+ # @param [String|Symbol] method The name of the method to call.
134
+ # @param [Array] args Array of arguments to pass to the method that is
135
+ # being called.
136
+ # @return [Mixed]
137
+ #
138
+ def call_method(method, *args)
139
+ return send(method, *args) if respond_to?(method)
140
+ end
141
+ end # DefinitionResolver
142
+ end # Helper
143
+ end # Rlint
@@ -0,0 +1,138 @@
1
+ module Rlint
2
+ module Helper
3
+ ##
4
+ # {Rlint::Helper::Scoping} is a helper module that can be used to more
5
+ # easily access scoping related information in subclasses of
6
+ # {Rlint::Callback}.
7
+ #
8
+ # Note that unlike {Rlint::Helper::DefinitionResolver} this method does not
9
+ # automatically update the `@scopes` array mentioned below, it merely
10
+ # creates the required variables and provides a few helper methods.
11
+ #
12
+ # ## Methods
13
+ #
14
+ # This module provides two methods:
15
+ #
16
+ # * scope: a method that can be used to retrieve the current
17
+ # scope/definition.
18
+ # * resolve_definition: a method that can be used to retrieve the
19
+ # scope/definition for the last constant in a constant path.
20
+ #
21
+ # ## Variables
22
+ #
23
+ # The following instance variables are created upon initializing a class
24
+ # that includes this module:
25
+ #
26
+ # * `@scopes`: an array that should be updated with instance of
27
+ # {Rlint::Definition} based on the current scope.
28
+ # * `@namespace`: array containing the constant names for the current
29
+ # namespace.
30
+ #
31
+ # The following keys are set in the `@storage` instance variable:
32
+ #
33
+ # * `:scope`: an instance of {Rlint::Definition} that will contain the
34
+ # definition list of the current block of code that's being analyzed.
35
+ #
36
+ module Scoping
37
+ ##
38
+ # @see Rlint::Callback#initialize
39
+ #
40
+ def initialize(*args)
41
+ super
42
+
43
+ @scopes = []
44
+ @namespace = []
45
+ @call_types = []
46
+
47
+ unless @storage[:scope].is_a?(Definition)
48
+ @storage[:scope] = Definition.new(nil, :lazy => true, :kernel => true)
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ ##
55
+ # Returns the scope/definition for the last segment in the specified
56
+ # constant path.
57
+ #
58
+ # @param [Array] path The constant path.
59
+ # @return [Rlint::Definition]
60
+ #
61
+ def resolve_definition(path)
62
+ current = scope
63
+
64
+ path.each do |segment|
65
+ found = current.lookup(:constant, segment)
66
+ current = found if found
67
+ end
68
+
69
+ return current
70
+ end
71
+
72
+ ##
73
+ # Checks if the specified token's name is a valid constant path.
74
+ #
75
+ # @param [Rlint::Token::VariableToken] token The token to validate.
76
+ # @return [TrueClass|FalseClass]
77
+ #
78
+ def valid_constant_path?(token)
79
+ current = scope
80
+
81
+ token.name.each do |segment|
82
+ found = current.lookup(:constant, segment)
83
+
84
+ if found and token.line > found.token.line
85
+ current = found
86
+ else
87
+ return false
88
+ end
89
+ end
90
+
91
+ return true
92
+ end
93
+
94
+ ##
95
+ # Checks if the specified type and token result in a valid
96
+ # {Rlint::Definition} instance.
97
+ #
98
+ # @param [#to_sym] type The type of data to look up.
99
+ # @param [Rlint::Token::VariableToken] token The token containing details
100
+ # about the variable.
101
+ # @param [Rlint::Definition] scope The scope to use for looking up the
102
+ # data.
103
+ # @return [TrueClass|FalseClass]
104
+ #
105
+ def definition_exists?(type, token, scope = scope)
106
+ found = scope.lookup(type, token.name)
107
+ has_line = found.respond_to?(:token) \
108
+ && !found.token.nil? \
109
+ && !found.token.line.nil?
110
+
111
+ if !found or (has_line and found.token.line > token.line)
112
+ return false
113
+ else
114
+ return true
115
+ end
116
+ end
117
+
118
+ ##
119
+ # Returns the call type to use for method calls.
120
+ #
121
+ # @return [Symbol]
122
+ #
123
+ def call_type
124
+ return !@call_types.empty? ? @call_types[-1] : :instance_method
125
+ end
126
+
127
+ ##
128
+ # Returns the current scope. This method is primarily used to make the
129
+ # code in this class a bit more pleasant to read.
130
+ #
131
+ # @return [Rlint::Definition]
132
+ #
133
+ def scope
134
+ return !@scopes.empty? ? @scopes[-1] : @storage[:scope]
135
+ end
136
+ end # Scoping
137
+ end # Helper
138
+ end # Rlint
@@ -0,0 +1,193 @@
1
+ module Rlint
2
+ ##
3
+ # {Rlint::Iterator} is a class that can be used to iterate over the AST
4
+ # generated by {Rlint::Parser} and execute callback methods for each
5
+ # encountered node. Basic usage is as following:
6
+ #
7
+ # code = <<-CODE
8
+ # [10, 20].each do |number|
9
+ # puts number
10
+ # end
11
+ # CODE
12
+ #
13
+ # parser = Rlint::Parser.new(code)
14
+ # tokens = parser.parse
15
+ # iterator = Rlint::Iterator.new
16
+ #
17
+ # iterator.run(tokens)
18
+ #
19
+ # This particular example doesn't do anything but iterating over the nodes
20
+ # due to no callback classes being defined. How to add these classes is
21
+ # discussed below.
22
+ #
23
+ # ## Callback Classes
24
+ #
25
+ # Without any custom callback classes the iterator class is fairly useless as
26
+ # it does nothing but iterate over all the nodes. These classes are defined
27
+ # as any ordinary class and are added to an interator instance using
28
+ # {Rlint::Iterator#bind}. At the most basic level each callback class should
29
+ # have the following structure:
30
+ #
31
+ # class MyCallback
32
+ # attr_reader :options
33
+ #
34
+ # def initialize(report = nil, storage = {})
35
+ # @report = report
36
+ # @storage = storage
37
+ # end
38
+ # end
39
+ #
40
+ # The constructor method should take two parameters: the first one is used
41
+ # for storing a instance of {Rlint::Report} (this parameter should be set to
42
+ # `nil` by default). The second parameter is a Hash containing custom data
43
+ # that is shared between callback classes bound to the same {Rlint::Iterator}
44
+ # instance. This Hash can be used to share, for example, definitions defined
45
+ # in callback class #1 with callback class #2.
46
+ #
47
+ # To make this, as well as adding errors and such to a report easier your own
48
+ # classes can extend {Rlint::Callback}:
49
+ #
50
+ # class MyCallback < Rlint::Callback
51
+ #
52
+ # end
53
+ #
54
+ # To add your class to an iterator instance you'd run the following:
55
+ #
56
+ # iterator = Rlint::Iterator.new
57
+ #
58
+ # iterator.bind(MyCallback)
59
+ #
60
+ # ## Callback Methods
61
+ #
62
+ # When iterating over an AST the method {Rlint::Iterator#iterator} calls two
63
+ # callback methods based on the event name stored in the token (in
64
+ # {Rlint::Token::Token#event}):
65
+ #
66
+ # * `on_EVENT_NAME`
67
+ # * `after_EVENT_NAME`
68
+ #
69
+ # Where `EVENT_NAME` is the name of the event. For example, for strings this
70
+ # would result in the following methods being called:
71
+ #
72
+ # * `on_string`
73
+ # * `after_string`
74
+ #
75
+ # Note that the "after" callback is not executed until all child nodes have
76
+ # been processed.
77
+ #
78
+ # Each method should take a single parameter that contains details about the
79
+ # token that is currently being processed. Each token is an instance of
80
+ # {Rlint::Token::Token} or one of its child classes.
81
+ #
82
+ # If you wanted to display the values of all strings in your console you'd
83
+ # write the following class:
84
+ #
85
+ # class StringPrinter < Rlint::Callback
86
+ # def on_string(token)
87
+ # puts token.value
88
+ # end
89
+ # end
90
+ #
91
+ class Iterator
92
+ ##
93
+ # Array containing a set of instance specific callback objects.
94
+ #
95
+ # @return [Array]
96
+ #
97
+ attr_reader :callbacks
98
+
99
+ ##
100
+ # Returns the Hash that is used by callback classes to store arbitrary
101
+ # data.
102
+ #
103
+ # @return [Hash]
104
+ #
105
+ attr_reader :storage
106
+
107
+ ##
108
+ # Creates a new instance of the iterator class.
109
+ #
110
+ # @param [Rlint::Report|NilClass] report The report to use, set to `nil` by
111
+ # default.
112
+ #
113
+ def initialize(report = nil)
114
+ @callbacks = []
115
+ @report = report
116
+ @storage = {}
117
+ end
118
+
119
+ ##
120
+ # Processes the entire AST for each callback class in sequence. For each
121
+ # callback class the method {Rlint::Iterator#iterate} is called to process
122
+ # an *entire* AST before moving on to the next callback class.
123
+ #
124
+ # @param [#each] nodes An array of nodes to process.
125
+ #
126
+ def run(nodes)
127
+ @callbacks.each do |obj|
128
+ execute_callback(obj, :on_start)
129
+
130
+ iterate(obj, nodes)
131
+
132
+ execute_callback(obj, :on_finish)
133
+ end
134
+ end
135
+
136
+ ##
137
+ # Processes an AST and calls callbacks methods for a specific callback
138
+ # object.
139
+ #
140
+ # @param [Rlint::Callback] callback_obj The callback object on which to
141
+ # invoke callback method.
142
+ # @param [#each] nodes An array (or a different object that responds to
143
+ # `#each()`) that contains a set of tokens to process.
144
+ #
145
+ def iterate(callback_obj, nodes)
146
+ nodes.each do |node|
147
+ next unless node.is_a?(Rlint::Token::Token)
148
+
149
+ event_name = node.event.to_s
150
+ callback_name = 'on_' + event_name
151
+ after_callback = 'after_' + event_name
152
+
153
+ execute_callback(callback_obj, callback_name, node)
154
+
155
+ node.child_nodes.each do |child_nodes|
156
+ iterate(callback_obj, child_nodes) if child_nodes.respond_to?(:each)
157
+ end
158
+
159
+ execute_callback(callback_obj, after_callback, node)
160
+ end
161
+ end
162
+
163
+ ##
164
+ # Adds the specified class to the list of callback classes for this
165
+ # instance.
166
+ #
167
+ # @example
168
+ # iterator = Rlint::Iterator.new
169
+ #
170
+ # iterator.bind(CustomCallbackClass)
171
+ #
172
+ # @param [Class] callback_class The class to add.
173
+ #
174
+ def bind(callback_class)
175
+ @callbacks << callback_class.new(@report, @storage)
176
+ end
177
+
178
+ private
179
+
180
+ ##
181
+ # Loops through all the bound callback classes and executes the specified
182
+ # callback method if it exists.
183
+ #
184
+ # @param [Rlint::Callback] obj The object on which to invoke the callback
185
+ # method.
186
+ # @param [String|Symbol] name The name of the callback method to execute.
187
+ # @param [Array] args Arguments to pass to the callback method.
188
+ #
189
+ def execute_callback(obj, name, *args)
190
+ obj.send(name, *args) if obj.respond_to?(name)
191
+ end
192
+ end # Iterator
193
+ end # Rlint