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.
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