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,103 @@
|
|
1
|
+
module Rlint
|
2
|
+
module Analyze
|
3
|
+
##
|
4
|
+
# {Rlint::Analyze::UnusedVariables} is used to check for unused local,
|
5
|
+
# instance, class and global variables.
|
6
|
+
#
|
7
|
+
class UnusedVariables < 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 are assigned but unused.'
|
16
|
+
|
17
|
+
##
|
18
|
+
# Hash containing the human readable names for various variable types.
|
19
|
+
#
|
20
|
+
# @return [Hash]
|
21
|
+
#
|
22
|
+
HUMAN_READABLE = {
|
23
|
+
:local_variable => 'local variable',
|
24
|
+
:instance_variable => 'instance variable',
|
25
|
+
:class_variable => 'class variable',
|
26
|
+
:global_variable => 'global variable'
|
27
|
+
}
|
28
|
+
|
29
|
+
##
|
30
|
+
# Array containing the variable callback methods to define.
|
31
|
+
#
|
32
|
+
# @return [Array]
|
33
|
+
#
|
34
|
+
UNUSED_VARIABLES = [
|
35
|
+
:on_local_variable,
|
36
|
+
:on_instance_variable,
|
37
|
+
:on_class_variable,
|
38
|
+
:on_global_variable
|
39
|
+
]
|
40
|
+
|
41
|
+
UNUSED_VARIABLES.each do |name|
|
42
|
+
define_method(name) do |token|
|
43
|
+
unused_variables.delete(token.name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# @see Rlint::Callback#initialize
|
49
|
+
#
|
50
|
+
def initialize(*args)
|
51
|
+
super
|
52
|
+
|
53
|
+
@unused_variables = [{}]
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Called when a new scope is found.
|
58
|
+
#
|
59
|
+
def on_new_scope
|
60
|
+
@unused_variables << {}
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Called after a new scope has ended.
|
65
|
+
#
|
66
|
+
def after_new_scope
|
67
|
+
# Add the warnings for all the unused variables.
|
68
|
+
unused_variables.each do |name, token|
|
69
|
+
readable = HUMAN_READABLE[token.type]
|
70
|
+
|
71
|
+
warning(
|
72
|
+
"assigned but unused #{readable} #{token.name}",
|
73
|
+
token.line,
|
74
|
+
token.column
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
78
|
+
@unused_variables.pop
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Called when a variable is assigned.
|
83
|
+
#
|
84
|
+
# @param [Rlint::Token::AssignmentToken] token
|
85
|
+
#
|
86
|
+
def on_assignment(token)
|
87
|
+
unused_variables[token.name] = token
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
##
|
93
|
+
# Returns the Hash to use for storing unused variables for the current
|
94
|
+
# scope.
|
95
|
+
#
|
96
|
+
# @return [Hash]
|
97
|
+
#
|
98
|
+
def unused_variables
|
99
|
+
return @unused_variables[-1]
|
100
|
+
end
|
101
|
+
end # UnusedVariables
|
102
|
+
end # Analyze
|
103
|
+
end # Rlint
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Rlint
|
2
|
+
##
|
3
|
+
# {Rlint::Callback} is a class that can be used (but you're not required to)
|
4
|
+
# to remove some common boilerplate code from custom callback classes.
|
5
|
+
#
|
6
|
+
# Using this class can be done by simply extending it:
|
7
|
+
#
|
8
|
+
# class MyCallback < Rlint::Callback
|
9
|
+
#
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# Once extended the following helper methods are provided:
|
13
|
+
#
|
14
|
+
# * {Rlint::Callback#error}
|
15
|
+
# * {Rlint::Callback#warning}
|
16
|
+
# * {Rlint::Callback#info}
|
17
|
+
#
|
18
|
+
# These 3 methods can be used to add data to a report. If no report is set
|
19
|
+
# the methods will not execute any code. This means your own code does not
|
20
|
+
# have to check for a valid instance of {Rlint::Report} in the `@report`
|
21
|
+
# instance variable every time you want to add data to it.
|
22
|
+
#
|
23
|
+
class Callback
|
24
|
+
##
|
25
|
+
# Creates a new instance of the class and stores the report.
|
26
|
+
#
|
27
|
+
# @param [Rlint::Report|NilClass] report The report instance to use.
|
28
|
+
# @param [Hash] options A hash containing custom options to set for the
|
29
|
+
# callback.
|
30
|
+
#
|
31
|
+
def initialize(report = nil, storage = {})
|
32
|
+
@report = report
|
33
|
+
@storage = storage
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
##
|
39
|
+
# Adds an error message to the report.
|
40
|
+
#
|
41
|
+
# @param [String] message The message to add.
|
42
|
+
# @param [Fixnum] line The line number of the message.
|
43
|
+
# @param [Fixnum] column The column number of the message.
|
44
|
+
#
|
45
|
+
def error(message, line, column)
|
46
|
+
@report.add(:error, message, line, column) if @report
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Adds a warning message to the report.
|
51
|
+
#
|
52
|
+
# @see Rlint::Callback#error
|
53
|
+
#
|
54
|
+
def warning(message, line, column)
|
55
|
+
@report.add(:warning, message, line, column) if @report
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Adds a regular informational message to the report.
|
60
|
+
#
|
61
|
+
# @see Rlint::Callback#error
|
62
|
+
#
|
63
|
+
def info(message, line, column)
|
64
|
+
@report.add(:info, message, line, column) if @report
|
65
|
+
end
|
66
|
+
end # Callback
|
67
|
+
end # Rlint
|
data/lib/rlint/cli.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Rlint
|
4
|
+
##
|
5
|
+
# {Rlint::CLI} is the commandline interface to Rlint.
|
6
|
+
#
|
7
|
+
class CLI
|
8
|
+
##
|
9
|
+
# Creates a new instance of the class and configures OptionParser.
|
10
|
+
#
|
11
|
+
def initialize
|
12
|
+
@formatters = constant_short_names(Rlint::Formatter)
|
13
|
+
@analyzers = constant_short_names(Rlint::Analyze)
|
14
|
+
|
15
|
+
@option_parser = OptionParser.new do |opts|
|
16
|
+
opts.banner = 'A static code analysis tool and linter for Ruby'
|
17
|
+
opts.program_name = 'rlint'
|
18
|
+
opts.version = Rlint::VERSION
|
19
|
+
opts.summary_indent = ' '
|
20
|
+
|
21
|
+
opts.separator ''
|
22
|
+
opts.separator 'Usage:'
|
23
|
+
opts.separator ' $ rlint [FILES] [OPTIONS]'
|
24
|
+
|
25
|
+
opts.separator ''
|
26
|
+
opts.separator 'Analyzers:'
|
27
|
+
opts.separator hash_to_list(@analyzers)
|
28
|
+
|
29
|
+
opts.separator ''
|
30
|
+
opts.separator 'Formatters:'
|
31
|
+
opts.separator hash_to_list(@formatters)
|
32
|
+
|
33
|
+
opts.separator ''
|
34
|
+
opts.separator 'Reporting Levels:'
|
35
|
+
opts.separator " error\n warning\n info"
|
36
|
+
|
37
|
+
opts.separator ''
|
38
|
+
opts.separator 'Options:'
|
39
|
+
|
40
|
+
opts.on(
|
41
|
+
'-f',
|
42
|
+
'--formatter=VALUE',
|
43
|
+
'The formatter to use',
|
44
|
+
String
|
45
|
+
) do |formatter|
|
46
|
+
if @formatters.key?(formatter)
|
47
|
+
Rlint.options.formatter = @formatters[formatter]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on(
|
52
|
+
'-l',
|
53
|
+
'--levels=VALUE',
|
54
|
+
'The reporting levels to enable',
|
55
|
+
Array
|
56
|
+
) do |levels|
|
57
|
+
Rlint.options.levels = levels.map { |level| level.to_sym }
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.on(
|
61
|
+
'-a',
|
62
|
+
'--analyzers=VALUE',
|
63
|
+
'The analyzers to enable',
|
64
|
+
Array
|
65
|
+
) do |names|
|
66
|
+
analyzers = Options::REQUIRED_ANALYZERS.dup
|
67
|
+
|
68
|
+
names.each do |name|
|
69
|
+
const = @analyzers[name]
|
70
|
+
|
71
|
+
if const and !analyzers.include?(const)
|
72
|
+
analyzers << const
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
Rlint.options.analyzers = analyzers
|
77
|
+
end
|
78
|
+
|
79
|
+
opts.on('-h', '--help', 'Shows this help message') do
|
80
|
+
puts @option_parser
|
81
|
+
exit
|
82
|
+
end
|
83
|
+
|
84
|
+
opts.on('-v', '--version', 'Shows the current version') do
|
85
|
+
version
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# Runs Rlint.
|
92
|
+
#
|
93
|
+
# @param [Array] argv Array of commandline parameters.
|
94
|
+
#
|
95
|
+
def run(argv = ARGV)
|
96
|
+
@option_parser.parse!(argv)
|
97
|
+
|
98
|
+
abort 'You have to specify a file to analyze' if argv.empty?
|
99
|
+
|
100
|
+
argv.each do |file|
|
101
|
+
abort "The file #{file} is not valid" unless File.file?(file)
|
102
|
+
|
103
|
+
code = File.read(file, File.size(file))
|
104
|
+
tokens = Parser.new(code, file).parse
|
105
|
+
report = Report.new(file, Rlint.options.levels)
|
106
|
+
iterator = Iterator.new(report)
|
107
|
+
formatter = Rlint.options.formatter.new
|
108
|
+
|
109
|
+
Rlint.options.analyzers.each { |const| iterator.bind(const) }
|
110
|
+
|
111
|
+
iterator.run(tokens)
|
112
|
+
|
113
|
+
output = formatter.format(report)
|
114
|
+
|
115
|
+
puts output unless output.empty?
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# Shows the current version of Rlint.
|
121
|
+
#
|
122
|
+
def version
|
123
|
+
puts "Rlint version #{Rlint::VERSION} running on #{RUBY_DESCRIPTION}"
|
124
|
+
exit
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
##
|
130
|
+
# Returns a hash containing various short names and the constants to use.
|
131
|
+
#
|
132
|
+
# @param [Class] constant The constant to use.
|
133
|
+
# @return [Hash]
|
134
|
+
#
|
135
|
+
def constant_short_names(constant)
|
136
|
+
hash = {}
|
137
|
+
|
138
|
+
constant.constants.sort.each do |const|
|
139
|
+
name = const.to_s.gsub(/([a-z]+)([A-Z]+)/, '\\1_\\2').downcase
|
140
|
+
hash[name] = constant.const_get(const)
|
141
|
+
end
|
142
|
+
|
143
|
+
return hash
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# Returns a string containing the names and descriptions of various
|
148
|
+
# constants formatted as a list.
|
149
|
+
#
|
150
|
+
# @param [Hash] hash
|
151
|
+
# @return [String]
|
152
|
+
#
|
153
|
+
def hash_to_list(hash)
|
154
|
+
longest = hash.keys.sort { |l, r| l.length <=> r.length }[-1].length
|
155
|
+
longest = longest > 32 ? longest : 32
|
156
|
+
list = []
|
157
|
+
|
158
|
+
hash.each do |name, const|
|
159
|
+
description = const.const_get(:DESCRIPTION)
|
160
|
+
|
161
|
+
list << " %-#{longest}s %s" % [name, description]
|
162
|
+
end
|
163
|
+
|
164
|
+
return list.join("\n")
|
165
|
+
end
|
166
|
+
end # CLI
|
167
|
+
end # Rlint
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Rlint
|
2
|
+
##
|
3
|
+
# {Rlint::ConstantImporter} is a module that can be used to create a list of
|
4
|
+
# method definitions (using {Rlint::Token::MethodDefinitionToken} for a
|
5
|
+
# supplied list of constant names.
|
6
|
+
#
|
7
|
+
module ConstantImporter
|
8
|
+
##
|
9
|
+
# Boolean that indicates if the Config constant should be ignored or not.
|
10
|
+
#
|
11
|
+
# @return [TrueClass|FalseClass]
|
12
|
+
#
|
13
|
+
IGNORE_CONFIG = Object.const_defined?(:RbConfig)
|
14
|
+
|
15
|
+
##
|
16
|
+
# Hash containing all the source Ruby methods to retrieve methods and the
|
17
|
+
# keys to store them under.
|
18
|
+
#
|
19
|
+
# @return [Hash]
|
20
|
+
#
|
21
|
+
METHOD_KEYS = {
|
22
|
+
:methods => :method,
|
23
|
+
:instance_methods => :instance_method,
|
24
|
+
:private_methods => :method,
|
25
|
+
:private_instance_methods => :instance_method
|
26
|
+
}
|
27
|
+
|
28
|
+
##
|
29
|
+
# Hash containing the parameter types as returned by `Method#parameters`
|
30
|
+
# and the attributes they should be stored in in an instance of
|
31
|
+
# {Rlint::Token::ParametersToken}.
|
32
|
+
#
|
33
|
+
# @return [Hash]
|
34
|
+
#
|
35
|
+
PARAMETER_KEYS = {
|
36
|
+
:req => :value,
|
37
|
+
:opt => :optional,
|
38
|
+
:rest => :rest,
|
39
|
+
:block => :block
|
40
|
+
}
|
41
|
+
|
42
|
+
##
|
43
|
+
# Imports the methods of a given list of constant names and returns a Hash
|
44
|
+
# containing instances of {Rlint::Definition} for each imported constant.
|
45
|
+
#
|
46
|
+
# @param [Array] constants An array of constant to import.
|
47
|
+
# @param [Mixed] source_constant The source constant to use for the
|
48
|
+
# `const_get` call. Set to `Object` by default.
|
49
|
+
# @return [Hash]
|
50
|
+
#
|
51
|
+
def self.import(constants, source_constant = Object)
|
52
|
+
imported = {}
|
53
|
+
|
54
|
+
constants.each do |name|
|
55
|
+
next if name == :Config and IGNORE_CONFIG
|
56
|
+
|
57
|
+
const = source_constant.const_get(name)
|
58
|
+
|
59
|
+
next unless const
|
60
|
+
|
61
|
+
name = name.to_s
|
62
|
+
scope = Definition.new(nil, :lazy => true, :constant => const)
|
63
|
+
|
64
|
+
METHOD_KEYS.each do |source, target|
|
65
|
+
next unless const.respond_to?(source)
|
66
|
+
|
67
|
+
const.send(source).each do |method|
|
68
|
+
token = Token::MethodDefinitionToken.new(:name => method.to_s)
|
69
|
+
|
70
|
+
if source =~ /^private/
|
71
|
+
token.visibility = :private
|
72
|
+
end
|
73
|
+
|
74
|
+
# Import all the parameters.
|
75
|
+
const.send(target, method).parameters.each do |param|
|
76
|
+
param_target = PARAMETER_KEYS[param[0]]
|
77
|
+
|
78
|
+
variable = Token::VariableToken.new(
|
79
|
+
:name => param[1].to_s,
|
80
|
+
:type => :local_variable
|
81
|
+
)
|
82
|
+
|
83
|
+
# Determine if the parameter should be appended to a list or set
|
84
|
+
# as the sole parameter.
|
85
|
+
if token.parameters.send(param_target).is_a?(Array)
|
86
|
+
token.parameters.send(param_target).push(variable)
|
87
|
+
else
|
88
|
+
token.parameters.send("#{param_target}=", variable)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
scope.add(target, method.to_s, Definition.new(nil, :token => token))
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
imported[name] = scope
|
97
|
+
end
|
98
|
+
|
99
|
+
return imported
|
100
|
+
end
|
101
|
+
end # ConstantImporter
|
102
|
+
end # Rlint
|
@@ -0,0 +1,230 @@
|
|
1
|
+
module Rlint
|
2
|
+
##
|
3
|
+
# {Rlint::Definition} is a class used for storing scoping/definition related
|
4
|
+
# information such as the methods that are available for various constants,
|
5
|
+
# variables that have been defined, etc.
|
6
|
+
#
|
7
|
+
# Each instance of this class can also have a number of parent scopes. These
|
8
|
+
# parent scopes can be used to look up data that is inherited in Ruby code
|
9
|
+
# (e.g. constants).
|
10
|
+
#
|
11
|
+
# Basic example of using this class:
|
12
|
+
#
|
13
|
+
# scope = Rlint::Definition.new
|
14
|
+
#
|
15
|
+
# scope.lookup(:local_variable, 'name') # => nil
|
16
|
+
#
|
17
|
+
# scope.add(:local_variable, 'name', 'Ruby')
|
18
|
+
#
|
19
|
+
# scope.lookup(:local_variable, 'name') # => "Ruby"
|
20
|
+
#
|
21
|
+
class Definition
|
22
|
+
##
|
23
|
+
# Array containing symbol names that should be looked up in the parent
|
24
|
+
# scopes if they're not found in the current scope.
|
25
|
+
#
|
26
|
+
# @return [Array]
|
27
|
+
#
|
28
|
+
LOOKUP_PARENT = [
|
29
|
+
:instance_variable,
|
30
|
+
:class_variable,
|
31
|
+
:global_variable,
|
32
|
+
:method,
|
33
|
+
:instance_method,
|
34
|
+
:constant
|
35
|
+
]
|
36
|
+
|
37
|
+
##
|
38
|
+
# Hash containing the default options and values to use when creating a new
|
39
|
+
# instance of this class.
|
40
|
+
#
|
41
|
+
# @return [Hash]
|
42
|
+
#
|
43
|
+
DEFAULT_OPTIONS = {
|
44
|
+
:lazy => false,
|
45
|
+
:kernel => false,
|
46
|
+
:constant => Object,
|
47
|
+
:reset => true
|
48
|
+
}
|
49
|
+
|
50
|
+
##
|
51
|
+
# Array containing the parent scopes, set to an empty Array by default.
|
52
|
+
#
|
53
|
+
# @return [Array]
|
54
|
+
#
|
55
|
+
attr_reader :parent
|
56
|
+
|
57
|
+
##
|
58
|
+
# Hash containing all the symbols (local variables, methods, etc) for the
|
59
|
+
# current scope instance.
|
60
|
+
#
|
61
|
+
# @return [Hash]
|
62
|
+
#
|
63
|
+
attr_reader :symbols
|
64
|
+
|
65
|
+
##
|
66
|
+
# The constant to lazy import child constants from, set to `Object` by
|
67
|
+
# default.
|
68
|
+
#
|
69
|
+
# @return [Mixed]
|
70
|
+
#
|
71
|
+
attr_reader :constant
|
72
|
+
|
73
|
+
##
|
74
|
+
# An array containing all the constant names that belong to the constant
|
75
|
+
# set in {Rlint::Definition#constant}. Each name is saved as a String.
|
76
|
+
#
|
77
|
+
# @return [Array]
|
78
|
+
#
|
79
|
+
attr_reader :constants
|
80
|
+
|
81
|
+
##
|
82
|
+
# Creates a new instance of the scope class and sets the default symbols.
|
83
|
+
#
|
84
|
+
# @param [Array|Rlint::Definition] parent The parent scope(s). Set this to
|
85
|
+
# an Array of {Rlint::Definition} instances to use multiple parent scopes.
|
86
|
+
# @param [Hash] options A hash containing custom options.
|
87
|
+
#
|
88
|
+
# @option options [TrueClass|FalseClass] :lazy When set to `true` missing
|
89
|
+
# constants will be lazy loaded.
|
90
|
+
# @option options [TrueClass|FalseClass] :kernel When set to `true` the
|
91
|
+
# Kernel constant will be loaded by default.
|
92
|
+
# @option options [Class] :constant The constant to use for importing other
|
93
|
+
# constants, set to `Object` by default.
|
94
|
+
# @option options [TrueClass|FalseClass] :reset When set to `true` the
|
95
|
+
# `value` attribute of the token will be set to `nil`
|
96
|
+
# @option options [Rlint::Token::Token] :token The token to set for the
|
97
|
+
# scope.
|
98
|
+
#
|
99
|
+
def initialize(parent = [], options = {})
|
100
|
+
parent = [parent] unless parent.is_a?(Array)
|
101
|
+
|
102
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
103
|
+
@parent = parent.select { |p| p.is_a?(Definition) }
|
104
|
+
@symbols = {
|
105
|
+
:local_variable => {},
|
106
|
+
:instance_variable => {},
|
107
|
+
:class_variable => {},
|
108
|
+
:global_variable => {},
|
109
|
+
:constant => {},
|
110
|
+
:method => {},
|
111
|
+
:instance_method => {}
|
112
|
+
}
|
113
|
+
|
114
|
+
if @options[:token]
|
115
|
+
self.token = @options[:token]
|
116
|
+
end
|
117
|
+
|
118
|
+
if options[:lazy] and options[:kernel]
|
119
|
+
@symbols[:constant] = ConstantImporter.import(['Kernel'])
|
120
|
+
end
|
121
|
+
|
122
|
+
# Import the default global variables.
|
123
|
+
if options[:kernel]
|
124
|
+
Kernel.global_variables.each do |name|
|
125
|
+
name = name.to_s
|
126
|
+
token = Token::VariableToken.new(
|
127
|
+
:name => name,
|
128
|
+
:type => :global_variable
|
129
|
+
)
|
130
|
+
|
131
|
+
add(:global_variable, name, Definition.new([], :token => token))
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
##
|
137
|
+
# Adds a new symbol to the scope.
|
138
|
+
#
|
139
|
+
# @param [#to_sym] type The type of symbol to add.
|
140
|
+
# @param [String] name The name of the symbol.
|
141
|
+
# @param [Mixed] value The value to store under the specified name.
|
142
|
+
#
|
143
|
+
def add(type, name, value = nil)
|
144
|
+
@symbols[type.to_sym][name] = value
|
145
|
+
end
|
146
|
+
|
147
|
+
##
|
148
|
+
# Looks up a symbol in the current and parent scopes (if there are any).
|
149
|
+
#
|
150
|
+
# @param [#to_sym] type The type of symbol to look up.
|
151
|
+
# @param [String] name The name of the symbol to look up.
|
152
|
+
#
|
153
|
+
def lookup(type, name)
|
154
|
+
name = name.to_s unless name.is_a?(String)
|
155
|
+
symbol = nil
|
156
|
+
type = type.to_sym
|
157
|
+
|
158
|
+
if @symbols[type] and @symbols[type][name]
|
159
|
+
symbol = @symbols[type][name]
|
160
|
+
# Look up the variable in the parent scope(s) (if any are set).
|
161
|
+
elsif lookup_parent?(type)
|
162
|
+
@parent.each do |parent|
|
163
|
+
parent_symbol = parent.lookup(type, name)
|
164
|
+
|
165
|
+
if parent_symbol
|
166
|
+
symbol = parent_symbol
|
167
|
+
break
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Lazy import the constant if it exists.
|
173
|
+
if !symbol and lazy_load?(name, type)
|
174
|
+
@symbols[:constant] = @symbols[:constant].merge(
|
175
|
+
ConstantImporter.import([name], @options[:constant])
|
176
|
+
)
|
177
|
+
|
178
|
+
symbol = lookup(type, name)
|
179
|
+
end
|
180
|
+
|
181
|
+
return symbol
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
# Returns the token associated with the scope.
|
186
|
+
#
|
187
|
+
# @return [Rlint::Token::Token|NilClass]
|
188
|
+
#
|
189
|
+
def token
|
190
|
+
return @options[:token]
|
191
|
+
end
|
192
|
+
|
193
|
+
##
|
194
|
+
# Sets the token of the scope.
|
195
|
+
#
|
196
|
+
# @param [Rlint::Token::Token] token The token to use.
|
197
|
+
#
|
198
|
+
def token=(token)
|
199
|
+
@options[:token] = token.dup
|
200
|
+
@options[:token].value = nil if @options[:reset]
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
##
|
206
|
+
# Returns a boolean that indicates if the specified symbol should be lazy
|
207
|
+
# loaded.
|
208
|
+
#
|
209
|
+
# @param [#to_sym] name The name of the symbol.
|
210
|
+
# @param [Symbol] type The type of the symbol.
|
211
|
+
# @return [TrueClass|FalseClass]
|
212
|
+
#
|
213
|
+
def lazy_load?(name, type)
|
214
|
+
return @options[:lazy] \
|
215
|
+
&& type == :constant \
|
216
|
+
&& @options[:constant].constants.include?(name.to_sym)
|
217
|
+
end
|
218
|
+
|
219
|
+
##
|
220
|
+
# Returns a boolean that indicates if the current symbol type should be
|
221
|
+
# looked up in a parent definition.
|
222
|
+
#
|
223
|
+
# @param [Symbol] type The type of symbol.
|
224
|
+
# @return [Trueclass|FalseClass]
|
225
|
+
#
|
226
|
+
def lookup_parent?(type)
|
227
|
+
return LOOKUP_PARENT.include?(type) && !@parent.empty?
|
228
|
+
end
|
229
|
+
end # Definition
|
230
|
+
end # Rlint
|