reek 5.0.2 → 5.1.0
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +9 -70
- data/.rubocop_todo.yml +63 -0
- data/.simplecov +4 -1
- data/CHANGELOG.md +5 -0
- data/Gemfile +12 -17
- data/README.md +21 -29
- data/Rakefile +2 -2
- data/docs/templates/default/docstring/setup.rb +3 -0
- data/features/command_line_interface/options.feature +5 -3
- data/features/configuration_files/schema_validation.feature +3 -3
- data/features/configuration_files/show_configuration_file.feature +44 -0
- data/features/rake_task/rake_task.feature +1 -1
- data/features/reports/json.feature +3 -3
- data/features/reports/reports.feature +4 -4
- data/features/reports/yaml.feature +3 -3
- data/features/rspec_matcher.feature +1 -0
- data/features/step_definitions/sample_file_steps.rb +6 -8
- data/features/todo_list.feature +39 -26
- data/lib/reek.rb +7 -0
- data/lib/reek/ast/node.rb +4 -0
- data/lib/reek/ast/sexp_extensions/if.rb +20 -0
- data/lib/reek/ast/sexp_extensions/methods.rb +1 -0
- data/lib/reek/cli/application.rb +25 -0
- data/lib/reek/cli/command/todo_list_command.rb +17 -7
- data/lib/reek/cli/options.rb +21 -14
- data/lib/reek/code_comment.rb +2 -0
- data/lib/reek/configuration/app_configuration.rb +0 -3
- data/lib/reek/configuration/configuration_converter.rb +4 -4
- data/lib/reek/configuration/directory_directives.rb +1 -0
- data/lib/reek/configuration/schema_validator.rb +1 -0
- data/lib/reek/context/method_context.rb +1 -0
- data/lib/reek/context/module_context.rb +5 -4
- data/lib/reek/context/visibility_tracker.rb +7 -4
- data/lib/reek/context_builder.rb +1 -0
- data/lib/reek/detector_repository.rb +1 -0
- data/lib/reek/errors/incomprehensible_source_error.rb +2 -2
- data/lib/reek/errors/syntax_error.rb +4 -0
- data/lib/reek/examiner.rb +1 -0
- data/lib/reek/report/text_report.rb +1 -0
- data/lib/reek/smell_detectors/control_parameter.rb +13 -107
- data/lib/reek/smell_detectors/control_parameter_helpers/call_in_condition_finder.rb +91 -0
- data/lib/reek/smell_detectors/control_parameter_helpers/candidate.rb +38 -0
- data/lib/reek/smell_detectors/control_parameter_helpers/control_parameter_finder.rb +94 -0
- data/lib/reek/smell_detectors/duplicate_method_call.rb +1 -0
- data/lib/reek/smell_detectors/feature_envy.rb +2 -0
- data/lib/reek/smell_detectors/irresponsible_module.rb +1 -0
- data/lib/reek/smell_detectors/long_parameter_list.rb +1 -0
- data/lib/reek/smell_detectors/manual_dispatch.rb +1 -0
- data/lib/reek/smell_detectors/missing_safe_method.rb +1 -0
- data/lib/reek/smell_detectors/nested_iterators.rb +1 -0
- data/lib/reek/smell_detectors/repeated_conditional.rb +1 -0
- data/lib/reek/smell_detectors/too_many_instance_variables.rb +1 -0
- data/lib/reek/smell_detectors/too_many_methods.rb +1 -0
- data/lib/reek/smell_detectors/too_many_statements.rb +1 -0
- data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +2 -0
- data/lib/reek/smell_detectors/unused_parameters.rb +1 -0
- data/lib/reek/source/source_locator.rb +1 -0
- data/lib/reek/spec/should_reek_of.rb +2 -2
- data/lib/reek/spec/should_reek_only_of.rb +1 -0
- data/lib/reek/spec/smell_matcher.rb +1 -0
- data/lib/reek/tree_dresser.rb +1 -0
- data/lib/reek/version.rb +1 -1
- data/samples/smelly_source/ruby.rb +368 -0
- data/spec/factories/factories.rb +10 -9
- data/spec/performance/reek/smell_detectors/runtime_speed_spec.rb +17 -0
- data/spec/quality/documentation_spec.rb +40 -0
- data/spec/reek/ast/sexp_extensions_spec.rb +20 -20
- data/spec/reek/cli/application_spec.rb +29 -0
- data/spec/reek/cli/command/todo_list_command_spec.rb +64 -46
- data/spec/reek/configuration/app_configuration_spec.rb +8 -8
- data/spec/reek/configuration/configuration_file_finder_spec.rb +3 -3
- data/spec/reek/configuration/schema_validator_spec.rb +10 -10
- data/spec/reek/detector_repository_spec.rb +2 -2
- data/spec/reek/smell_detectors/control_parameter_spec.rb +17 -0
- data/spec/reek/source/source_locator_spec.rb +0 -2
- data/spec/spec_helper.rb +2 -0
- data/tasks/configuration.rake +2 -1
- data/tasks/test.rake +4 -0
- metadata +11 -5
- data/ataru_setup.rb +0 -13
- data/tasks/ataru.rake +0 -5
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './call_in_condition_finder'
|
4
|
+
require_relative '../../ast/node'
|
5
|
+
|
6
|
+
module Reek
|
7
|
+
module SmellDetectors
|
8
|
+
module ControlParameterHelpers
|
9
|
+
# Finds cases of ControlParameter in a particular node for a particular parameter
|
10
|
+
class ControlParameterFinder
|
11
|
+
CONDITIONAL_NODE_TYPES = [:if, :case, :and, :or].freeze
|
12
|
+
#
|
13
|
+
# @param node [Reek::AST::Node] the node in our current scope,
|
14
|
+
# e.g. s(:def, :alfa,
|
15
|
+
# s(:args,
|
16
|
+
# s(:arg, :bravo),
|
17
|
+
# @param parameter [Symbol] the parameter name in question
|
18
|
+
# e.g. in the example above this would be :bravo
|
19
|
+
#
|
20
|
+
def initialize(node, parameter)
|
21
|
+
@node = node
|
22
|
+
@parameter = parameter
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# @return [Array<Reek::AST::Node>] all nodes where the parameter is used for control flow
|
27
|
+
#
|
28
|
+
def find_matches
|
29
|
+
return [] if legitimate_uses?
|
30
|
+
|
31
|
+
nested_finders.flat_map(&:find_matches) + uses_of_param_in_condition
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# @return [Boolean] true if the parameter is not used for control flow
|
36
|
+
#
|
37
|
+
def legitimate_uses?
|
38
|
+
return true if CallInConditionFinder.new(node, parameter).uses_param_in_call_in_condition?
|
39
|
+
return true if parameter_used_in_body?
|
40
|
+
return true if nested_finders.any?(&:legitimate_uses?)
|
41
|
+
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_reader :node, :parameter
|
48
|
+
|
49
|
+
#
|
50
|
+
# @return [Boolean] if the parameter is used in the body of the method
|
51
|
+
# e.g. this
|
52
|
+
#
|
53
|
+
# def alfa(bravo)
|
54
|
+
# puts bravo
|
55
|
+
# if bravo then charlie end
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# would cause this method to return true because of the "puts bravo"
|
59
|
+
#
|
60
|
+
def parameter_used_in_body?
|
61
|
+
nodes = node.body_nodes([:lvar], CONDITIONAL_NODE_TYPES)
|
62
|
+
nodes.any? { |lvar_node| lvar_node.var_name == parameter }
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# @return [Array<ControlParameterFinder>]
|
67
|
+
#
|
68
|
+
def nested_finders
|
69
|
+
@nested_finders ||= conditional_nodes.flat_map do |node|
|
70
|
+
self.class.new(node, parameter)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# @return [Array<Reek::AST::Node>] all nodes where the parameter is part of a condition,
|
76
|
+
# e.g. [s(:lvar, :charlie)]
|
77
|
+
#
|
78
|
+
def uses_of_param_in_condition
|
79
|
+
condition = node.condition
|
80
|
+
return [] unless condition
|
81
|
+
|
82
|
+
condition.each_node(:lvar).select { |inner| inner.var_name == parameter }
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# @return [Array<Reek::AST::Node>] the conditional nodes scoped below the current node
|
87
|
+
#
|
88
|
+
def conditional_nodes
|
89
|
+
node.body_nodes(CONDITIONAL_NODE_TYPES)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -45,6 +45,7 @@ module Reek
|
|
45
45
|
def sniff
|
46
46
|
return [] if context.singleton_method? || context.module_function?
|
47
47
|
return [] unless context.references_self?
|
48
|
+
|
48
49
|
envious_receivers.map do |name, lines|
|
49
50
|
smell_warning(
|
50
51
|
lines: lines,
|
@@ -61,6 +62,7 @@ module Reek
|
|
61
62
|
|
62
63
|
def envious_receivers
|
63
64
|
return {} if refs.self_is_max?
|
65
|
+
|
64
66
|
refs.most_popular
|
65
67
|
end
|
66
68
|
end
|
@@ -96,6 +96,7 @@ module Reek
|
|
96
96
|
# @quality :reek:TooManyStatements { max_statements: 6 }
|
97
97
|
def scout(exp:, depth:)
|
98
98
|
return [] unless exp
|
99
|
+
|
99
100
|
# Find all non-nested blocks in this expression
|
100
101
|
exp.each_node([:block], [:block]).flat_map do |iterator|
|
101
102
|
new_depth = increment_depth(iterator, depth)
|
@@ -111,6 +111,7 @@ module Reek
|
|
111
111
|
end
|
112
112
|
|
113
113
|
return unless arg_search_exp
|
114
|
+
|
114
115
|
args_nodes = arg_search_exp.each_node(:args, [:class, :module, :defs, :def])
|
115
116
|
|
116
117
|
args_nodes.each do |args_node|
|
@@ -133,6 +134,7 @@ module Reek
|
|
133
134
|
def record_variable_name(exp, symbol, accumulator)
|
134
135
|
varname = symbol.to_s.sub(/^\*/, '')
|
135
136
|
return if varname == ''
|
137
|
+
|
136
138
|
var = varname.to_sym
|
137
139
|
accumulator[var].push(exp.line)
|
138
140
|
end
|
@@ -20,7 +20,7 @@ module Reek
|
|
20
20
|
configuration = Configuration::AppConfiguration.default)
|
21
21
|
@smell_type = smell_type.to_s
|
22
22
|
@smell_details = smell_details
|
23
|
-
configuration.load_values(
|
23
|
+
configuration.load_values(DETECTORS_KEY =>
|
24
24
|
{ smell_type => { SmellConfiguration::ENABLED_KEY => true } })
|
25
25
|
@configuration = configuration
|
26
26
|
end
|
@@ -36,7 +36,7 @@ module Reek
|
|
36
36
|
|
37
37
|
def with_config(config_hash)
|
38
38
|
new_configuration = Configuration::AppConfiguration.default
|
39
|
-
new_configuration.load_values(
|
39
|
+
new_configuration.load_values(DETECTORS_KEY =>
|
40
40
|
{ smell_type => config_hash })
|
41
41
|
self.class.new(smell_type, smell_details, new_configuration)
|
42
42
|
end
|
@@ -39,6 +39,7 @@ module Reek
|
|
39
39
|
parameter_keys = other_attributes.keys - COMPARABLE_ATTRIBUTES
|
40
40
|
extra_keys = parameter_keys - smell_warning.parameters.keys
|
41
41
|
return if extra_keys.empty?
|
42
|
+
|
42
43
|
raise ArgumentError, "The attribute '#{extra_keys.first}' is not available for comparison"
|
43
44
|
end
|
44
45
|
|
data/lib/reek/tree_dresser.rb
CHANGED
@@ -41,6 +41,7 @@ module Reek
|
|
41
41
|
# @quality :reek:TooManyStatements { max_statements: 6 }
|
42
42
|
def dress(sexp, comment_map)
|
43
43
|
return sexp unless sexp.is_a? ::Parser::AST::Node
|
44
|
+
|
44
45
|
type = sexp.type
|
45
46
|
children = sexp.children.map { |child| dress(child, comment_map) }
|
46
47
|
comments = comment_map[sexp]
|
data/lib/reek/version.rb
CHANGED
@@ -0,0 +1,368 @@
|
|
1
|
+
module CodeRay
|
2
|
+
module Scanners
|
3
|
+
|
4
|
+
# This scanner is really complex, since Ruby _is_ a complex language!
|
5
|
+
#
|
6
|
+
# It tries to highlight 100% of all common code,
|
7
|
+
# and 90% of strange codes.
|
8
|
+
#
|
9
|
+
# It is optimized for HTML highlighting, and is not very useful for
|
10
|
+
# parsing or pretty printing.
|
11
|
+
#
|
12
|
+
# For now, I think it's better than the scanners in VIM or Syntax, or
|
13
|
+
# any highlighter I was able to find, except Caleb's RubyLexer.
|
14
|
+
#
|
15
|
+
# I hope it's also better than the rdoc/irb lexer.
|
16
|
+
class Ruby < Scanner
|
17
|
+
|
18
|
+
include Streamable
|
19
|
+
|
20
|
+
register_for :ruby
|
21
|
+
file_extension 'rb'
|
22
|
+
|
23
|
+
helper :patterns
|
24
|
+
|
25
|
+
private
|
26
|
+
def scan_tokens tokens, options
|
27
|
+
last_token_dot = false
|
28
|
+
value_expected = true
|
29
|
+
heredocs = nil
|
30
|
+
last_state = nil
|
31
|
+
state = :initial
|
32
|
+
depth = nil
|
33
|
+
inline_block_stack = []
|
34
|
+
|
35
|
+
patterns = Patterns # avoid constant lookup
|
36
|
+
|
37
|
+
until eos?
|
38
|
+
match = nil
|
39
|
+
kind = nil
|
40
|
+
|
41
|
+
if state.instance_of? patterns::StringState
|
42
|
+
# {{{
|
43
|
+
match = scan_until(state.pattern) || scan_until(/\z/)
|
44
|
+
tokens << [match, :content] unless match.empty?
|
45
|
+
break if eos?
|
46
|
+
|
47
|
+
if state.heredoc and self[1] # end of heredoc
|
48
|
+
match = getch.to_s
|
49
|
+
match << scan_until(/$/) unless eos?
|
50
|
+
tokens << [match, :delimiter]
|
51
|
+
tokens << [:close, state.type]
|
52
|
+
state = state.next_state
|
53
|
+
next
|
54
|
+
end
|
55
|
+
|
56
|
+
case match = getch
|
57
|
+
|
58
|
+
when state.delim
|
59
|
+
if state.paren
|
60
|
+
state.paren_depth -= 1
|
61
|
+
if state.paren_depth > 0
|
62
|
+
tokens << [match, :nesting_delimiter]
|
63
|
+
next
|
64
|
+
end
|
65
|
+
end
|
66
|
+
tokens << [match, :delimiter]
|
67
|
+
if state.type == :regexp and not eos?
|
68
|
+
modifiers = scan(/#{patterns::REGEXP_MODIFIERS}/ox)
|
69
|
+
tokens << [modifiers, :modifier] unless modifiers.empty?
|
70
|
+
end
|
71
|
+
tokens << [:close, state.type]
|
72
|
+
value_expected = false
|
73
|
+
state = state.next_state
|
74
|
+
|
75
|
+
when '\\'
|
76
|
+
if state.interpreted
|
77
|
+
if esc = scan(/ #{patterns::ESCAPE} /ox)
|
78
|
+
tokens << [match + esc, :char]
|
79
|
+
else
|
80
|
+
tokens << [match, :error]
|
81
|
+
end
|
82
|
+
else
|
83
|
+
case m = getch
|
84
|
+
when state.delim, '\\'
|
85
|
+
tokens << [match + m, :char]
|
86
|
+
when nil
|
87
|
+
tokens << [match, :error]
|
88
|
+
else
|
89
|
+
tokens << [match + m, :content]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
when '#'
|
94
|
+
case peek(1)
|
95
|
+
when '{'
|
96
|
+
inline_block_stack << [state, depth, heredocs]
|
97
|
+
value_expected = true
|
98
|
+
state = :initial
|
99
|
+
depth = 1
|
100
|
+
tokens << [:open, :inline]
|
101
|
+
tokens << [match + getch, :inline_delimiter]
|
102
|
+
when '$', '@'
|
103
|
+
tokens << [match, :escape]
|
104
|
+
last_state = state # scan one token as normal code, then return here
|
105
|
+
state = :initial
|
106
|
+
else
|
107
|
+
raise_inspect 'else-case # reached; #%p not handled' % peek(1), tokens
|
108
|
+
end
|
109
|
+
|
110
|
+
when state.paren
|
111
|
+
state.paren_depth += 1
|
112
|
+
tokens << [match, :nesting_delimiter]
|
113
|
+
|
114
|
+
when /#{patterns::REGEXP_SYMBOLS}/ox
|
115
|
+
tokens << [match, :function]
|
116
|
+
|
117
|
+
else
|
118
|
+
raise_inspect 'else-case " reached; %p not handled, state = %p' % [match, state], tokens
|
119
|
+
|
120
|
+
end
|
121
|
+
next
|
122
|
+
# }}}
|
123
|
+
else
|
124
|
+
# {{{
|
125
|
+
if match = scan(/[ \t\f]+/)
|
126
|
+
kind = :space
|
127
|
+
match << scan(/\s*/) unless eos? or heredocs
|
128
|
+
tokens << [match, kind]
|
129
|
+
next
|
130
|
+
|
131
|
+
elsif match = scan(/\\?\n/)
|
132
|
+
kind = :space
|
133
|
+
if match == "\n"
|
134
|
+
value_expected = true # FIXME not quite true
|
135
|
+
state = :initial if state == :undef_comma_expected
|
136
|
+
end
|
137
|
+
if heredocs
|
138
|
+
unscan # heredoc scanning needs \n at start
|
139
|
+
state = heredocs.shift
|
140
|
+
tokens << [:open, state.type]
|
141
|
+
heredocs = nil if heredocs.empty?
|
142
|
+
next
|
143
|
+
else
|
144
|
+
match << scan(/\s*/) unless eos?
|
145
|
+
end
|
146
|
+
tokens << [match, kind]
|
147
|
+
next
|
148
|
+
|
149
|
+
elsif match = scan(/\#.*/) or
|
150
|
+
( bol? and match = scan(/#{patterns::RUBYDOC_OR_DATA}/o) )
|
151
|
+
kind = :comment
|
152
|
+
value_expected = true
|
153
|
+
tokens << [match, kind]
|
154
|
+
next
|
155
|
+
|
156
|
+
elsif state == :initial
|
157
|
+
|
158
|
+
# IDENTS #
|
159
|
+
if match = scan(/#{patterns::METHOD_NAME}/o)
|
160
|
+
if last_token_dot
|
161
|
+
kind = if match[/^[A-Z]/] and not match?(/\(/) then :constant else :ident end
|
162
|
+
else
|
163
|
+
kind = patterns::IDENT_KIND[match]
|
164
|
+
if kind == :ident and match[/^[A-Z]/] and not match[/[!?]$/] and not match?(/\(/)
|
165
|
+
kind = :constant
|
166
|
+
elsif kind == :reserved
|
167
|
+
state = patterns::DEF_NEW_STATE[match]
|
168
|
+
end
|
169
|
+
end
|
170
|
+
## experimental!
|
171
|
+
value_expected = :set if
|
172
|
+
patterns::REGEXP_ALLOWED[match] or check(/#{patterns::VALUE_FOLLOWS}/o)
|
173
|
+
|
174
|
+
elsif last_token_dot and match = scan(/#{patterns::METHOD_NAME_OPERATOR}/o)
|
175
|
+
kind = :ident
|
176
|
+
value_expected = :set if check(/#{patterns::VALUE_FOLLOWS}/o)
|
177
|
+
|
178
|
+
# OPERATORS #
|
179
|
+
elsif not last_token_dot and match = scan(/ \.\.\.? | (?:\.|::)() | [,\(\)\[\]\{\}] | ==?=? /x)
|
180
|
+
if match !~ / [.\)\]\}] /x or match =~ /\.\.\.?/
|
181
|
+
value_expected = :set
|
182
|
+
end
|
183
|
+
last_token_dot = :set if self[1]
|
184
|
+
kind = :operator
|
185
|
+
unless inline_block_stack.empty?
|
186
|
+
case match
|
187
|
+
when '{'
|
188
|
+
depth += 1
|
189
|
+
when '}'
|
190
|
+
depth -= 1
|
191
|
+
if depth == 0 # closing brace of inline block reached
|
192
|
+
state, depth, heredocs = inline_block_stack.pop
|
193
|
+
tokens << [match, :inline_delimiter]
|
194
|
+
kind = :inline
|
195
|
+
match = :close
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
elsif match = scan(/ ['"] /mx)
|
201
|
+
tokens << [:open, :string]
|
202
|
+
kind = :delimiter
|
203
|
+
state = patterns::StringState.new :string, match == '"', match # important for streaming
|
204
|
+
|
205
|
+
elsif match = scan(/#{patterns::INSTANCE_VARIABLE}/o)
|
206
|
+
kind = :instance_variable
|
207
|
+
|
208
|
+
elsif value_expected and match = scan(/\//)
|
209
|
+
tokens << [:open, :regexp]
|
210
|
+
kind = :delimiter
|
211
|
+
interpreted = true
|
212
|
+
state = patterns::StringState.new :regexp, interpreted, match
|
213
|
+
|
214
|
+
elsif match = scan(/#{patterns::NUMERIC}/o)
|
215
|
+
kind = if self[1] then :float else :integer end
|
216
|
+
|
217
|
+
elsif match = scan(/#{patterns::SYMBOL}/o)
|
218
|
+
case delim = match[1]
|
219
|
+
when ?', ?"
|
220
|
+
tokens << [:open, :symbol]
|
221
|
+
tokens << [':', :symbol]
|
222
|
+
match = delim.chr
|
223
|
+
kind = :delimiter
|
224
|
+
state = patterns::StringState.new :symbol, delim == ?", match
|
225
|
+
else
|
226
|
+
kind = :symbol
|
227
|
+
end
|
228
|
+
|
229
|
+
elsif match = scan(/ [-+!~^]=? | [*|&]{1,2}=? | >>? /x)
|
230
|
+
value_expected = :set
|
231
|
+
kind = :operator
|
232
|
+
|
233
|
+
elsif value_expected and match = scan(/#{patterns::HEREDOC_OPEN}/o)
|
234
|
+
indented = self[1] == '-'
|
235
|
+
quote = self[3]
|
236
|
+
delim = self[quote ? 4 : 2]
|
237
|
+
kind = patterns::QUOTE_TO_TYPE[quote]
|
238
|
+
tokens << [:open, kind]
|
239
|
+
tokens << [match, :delimiter]
|
240
|
+
match = :close
|
241
|
+
heredoc = patterns::StringState.new kind, quote != '\'', delim, (indented ? :indented : :linestart )
|
242
|
+
heredocs ||= [] # create heredocs if empty
|
243
|
+
heredocs << heredoc
|
244
|
+
|
245
|
+
elsif value_expected and match = scan(/#{patterns::FANCY_START_CORRECT}/o)
|
246
|
+
kind, interpreted = *patterns::FancyStringType.fetch(self[1]) do
|
247
|
+
raise_inspect 'Unknown fancy string: %%%p' % k, tokens
|
248
|
+
end
|
249
|
+
tokens << [:open, kind]
|
250
|
+
state = patterns::StringState.new kind, interpreted, self[2]
|
251
|
+
kind = :delimiter
|
252
|
+
|
253
|
+
elsif value_expected and match = scan(/#{patterns::CHARACTER}/o)
|
254
|
+
kind = :integer
|
255
|
+
|
256
|
+
elsif match = scan(/ [\/%]=? | <(?:<|=>?)? | [?:;] /x)
|
257
|
+
value_expected = :set
|
258
|
+
kind = :operator
|
259
|
+
|
260
|
+
elsif match = scan(/`/)
|
261
|
+
if last_token_dot
|
262
|
+
kind = :operator
|
263
|
+
else
|
264
|
+
tokens << [:open, :shell]
|
265
|
+
kind = :delimiter
|
266
|
+
state = patterns::StringState.new :shell, true, match
|
267
|
+
end
|
268
|
+
|
269
|
+
elsif match = scan(/#{patterns::GLOBAL_VARIABLE}/o)
|
270
|
+
kind = :global_variable
|
271
|
+
|
272
|
+
elsif match = scan(/#{patterns::CLASS_VARIABLE}/o)
|
273
|
+
kind = :class_variable
|
274
|
+
|
275
|
+
else
|
276
|
+
kind = :error
|
277
|
+
match = getch
|
278
|
+
|
279
|
+
end
|
280
|
+
|
281
|
+
elsif state == :def_expected
|
282
|
+
state = :initial
|
283
|
+
if match = scan(/(?>#{patterns::METHOD_NAME_EX})(?!\.|::)/o)
|
284
|
+
kind = :method
|
285
|
+
else
|
286
|
+
next
|
287
|
+
end
|
288
|
+
|
289
|
+
elsif state == :undef_expected
|
290
|
+
state = :undef_comma_expected
|
291
|
+
if match = scan(/#{patterns::METHOD_NAME_EX}/o)
|
292
|
+
kind = :method
|
293
|
+
elsif match = scan(/#{patterns::SYMBOL}/o)
|
294
|
+
case delim = match[1]
|
295
|
+
when ?', ?"
|
296
|
+
tokens << [:open, :symbol]
|
297
|
+
tokens << [':', :symbol]
|
298
|
+
match = delim.chr
|
299
|
+
kind = :delimiter
|
300
|
+
state = patterns::StringState.new :symbol, delim == ?", match
|
301
|
+
state.next_state = :undef_comma_expected
|
302
|
+
else
|
303
|
+
kind = :symbol
|
304
|
+
end
|
305
|
+
else
|
306
|
+
state = :initial
|
307
|
+
next
|
308
|
+
end
|
309
|
+
|
310
|
+
elsif state == :undef_comma_expected
|
311
|
+
if match = scan(/,/)
|
312
|
+
kind = :operator
|
313
|
+
state = :undef_expected
|
314
|
+
else
|
315
|
+
state = :initial
|
316
|
+
next
|
317
|
+
end
|
318
|
+
|
319
|
+
elsif state == :module_expected
|
320
|
+
if match = scan(/<</)
|
321
|
+
kind = :operator
|
322
|
+
else
|
323
|
+
state = :initial
|
324
|
+
if match = scan(/ (?:#{patterns::IDENT}::)* #{patterns::IDENT} /ox)
|
325
|
+
kind = :class
|
326
|
+
else
|
327
|
+
next
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
# }}}
|
333
|
+
|
334
|
+
value_expected = value_expected == :set
|
335
|
+
last_token_dot = last_token_dot == :set
|
336
|
+
|
337
|
+
if $DEBUG and not kind
|
338
|
+
raise_inspect 'Error token %p in line %d' %
|
339
|
+
[[match, kind], line], tokens, state
|
340
|
+
end
|
341
|
+
raise_inspect 'Empty token', tokens unless match
|
342
|
+
|
343
|
+
tokens << [match, kind]
|
344
|
+
|
345
|
+
if last_state
|
346
|
+
state = last_state
|
347
|
+
last_state = nil
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
inline_block_stack << [state] if state.is_a? patterns::StringState
|
353
|
+
until inline_block_stack.empty?
|
354
|
+
this_block = inline_block_stack.pop
|
355
|
+
tokens << [:close, :inline] if this_block.size > 1
|
356
|
+
state = this_block.first
|
357
|
+
tokens << [:close, state.type]
|
358
|
+
end
|
359
|
+
|
360
|
+
tokens
|
361
|
+
end
|
362
|
+
|
363
|
+
end
|
364
|
+
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
# vim:fdm=marker
|