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