lib/rubocop/cop/surrounding_space.rb in rubocop-0.7.2 vs lib/rubocop/cop/surrounding_space.rb in rubocop-0.8.0
- old
+ new
@@ -1,180 +1,281 @@
# encoding: utf-8
+# rubocop:disable SymbolName
+
module Rubocop
module Cop
module SurroundingSpace
- def inspect(file, source, tokens, sexp)
- @correlations.sort.each do |ix, grammar_path|
- check_missing_space(tokens, ix, grammar_path)
+ def space_between?(t1, t2)
+ char_preceding_2nd_token =
+ @source[t2.pos.line - 1][t2.pos.column - 1]
+ if char_preceding_2nd_token == '+' && t1.type != :tPLUS
+ # Special case. A unary plus is not present in the tokens.
+ char_preceding_2nd_token =
+ @source[t2.pos.line - 1][t2.pos.column - 2]
end
- tokens.each_index { |ix| check_unwanted_space(tokens, ix) }
+ t2.pos.line > t1.pos.line || char_preceding_2nd_token == ' '
end
- private
-
- def previous_non_space(tokens, ix)
- tokens[0...ix].reverse.find { |t| !whitespace?(t) }
+ def index_of_first_token(node, tokens)
+ @token_table ||= build_token_table(tokens)
+ b = node.loc.expression.begin
+ @token_table[[b.line, b.column]]
end
- def ok_without_spaces?(grammar_path)
- parent, child = grammar_path.values_at(-2, -1)
- return true if [:unary, :symbol, :defs, :def, :call].include?(parent)
- return true if [:**, :block_var].include?(child)
- parent == :command_call && child == :'::'
+ def index_of_last_token(node, tokens)
+ @token_table ||= build_token_table(tokens)
+ e = node.loc.expression.end
+ (0...e.column).to_a.reverse.find do |c|
+ ix = @token_table[[e.line, c]]
+ return ix if ix
+ end
end
- def surrounded_by_whitespace?(nearby_tokens)
- left, _, right = nearby_tokens
- whitespace?(left) && whitespace?(right)
+ def build_token_table(tokens)
+ table = {}
+ tokens.each_with_index do |t, ix|
+ table[[t.pos.line, t.pos.column]] = ix
+ end
+ table
end
-
- # Default implementation for classes that don't need it.
- def check_missing_space(tokens, ix, grammar_path)
- end
end
class SpaceAroundOperators < Cop
include SurroundingSpace
- ERROR_MESSAGE = 'Surrounding space missing for operator '
+ MSG_MISSING = "Surrounding space missing for operator '%s'."
+ MSG_DETECTED = 'Space around operator ** detected.'
- def check_missing_space(tokens, ix, grammar_path)
- t = tokens[ix]
- if t.type == :on_op
- unless surrounded_by_whitespace?(tokens[ix - 1, 3])
- unless ok_without_spaces?(grammar_path)
- add_offence(:convention, t.pos.lineno,
- ERROR_MESSAGE + "'#{t.text}'.")
+ BINARY_OPERATORS =
+ [:tEQL, :tAMPER2, :tPIPE, :tCARET, :tPLUS, :tMINUS, :tSTAR2,
+ :tDIVIDE, :tPERCENT, :tEH, :tCOLON, :tANDOP, :tOROP, :tMATCH,
+ :tNMATCH, :tEQ, :tNEQ, :tGT, :tRSHFT, :tGEQ, :tLT,
+ :tLSHFT, :tLEQ, :tASSOC, :tEQQ, :tCMP, :tOP_ASGN]
+
+ def inspect(source, tokens, sexp, comments)
+ @source = source
+ positions_not_to_check = get_positions_not_to_check(tokens, sexp)
+
+ tokens.each_cons(3) do |token_before, token, token_after|
+ next if token_before.type == :kDEF # TODO: remove?
+ next if positions_not_to_check.include?(token.pos)
+
+ case token.type
+ when :tPOW
+ if has_space?(token_before, token, token_after)
+ add_offence(:convention, token.pos.line, MSG_DETECTED)
end
+ when *BINARY_OPERATORS
+ check_missing_space(token_before, token, token_after)
end
end
end
- def check_unwanted_space(tokens, ix)
- prev, t, nxt = tokens.values_at(ix - 1, ix, ix + 1)
- if t.type == :on_op && t.text == '**' &&
- (whitespace?(prev) || whitespace?(nxt))
- add_offence(:convention, t.pos.lineno,
- "Space around operator #{t.text} detected.")
+ # Returns an array of positions marking the tokens that this cop
+ # should not check, either because the token is not an operator
+ # or because another cop does the check.
+ def get_positions_not_to_check(tokens, sexp)
+ positions_not_to_check = []
+ do_not_check_block_arg_pipes(sexp, positions_not_to_check)
+ do_not_check_param_default(tokens, sexp, positions_not_to_check)
+ do_not_check_class_lshift_self(tokens, sexp, positions_not_to_check)
+ do_not_check_def_things(tokens, sexp, positions_not_to_check)
+ do_not_check_singleton_operator_defs(tokens, sexp,
+ positions_not_to_check)
+ positions_not_to_check
+ end
+
+ def do_not_check_block_arg_pipes(sexp, positions_not_to_check)
+ # each { |a| }
+ # ^ ^
+ on_node(:block, sexp) do |b|
+ on_node(:args, b) do |a|
+ positions_not_to_check << a.loc.begin << a.loc.end if a.loc.begin
+ end
end
end
+
+ def do_not_check_param_default(tokens, sexp, positions_not_to_check)
+ # func(a, b=nil)
+ # ^
+ on_node(:optarg, sexp) do |optarg|
+ _arg, equals, _value = tokens[index_of_first_token(optarg, tokens),
+ 3]
+ positions_not_to_check << equals.pos
+ end
+ end
+
+ def do_not_check_class_lshift_self(tokens, sexp, positions_not_to_check)
+ # class <<self
+ # ^
+ on_node(:sclass, sexp) do |sclass|
+ ix = index_of_first_token(sclass, tokens)
+ if tokens[ix, 2].map(&:type) == [:kCLASS, :tLSHFT]
+ positions_not_to_check << tokens[ix + 1].pos
+ end
+ end
+ end
+
+ def do_not_check_def_things(tokens, sexp, positions_not_to_check)
+ # def +(other)
+ # ^
+ on_node(:def, sexp) do |def_node|
+ # def each &block
+ # ^
+ # def each *args
+ # ^
+ on_node([:blockarg, :restarg], def_node) do |arg_node|
+ positions_not_to_check << tokens[index_of_first_token(arg_node,
+ tokens)].pos
+ end
+ positions_not_to_check <<
+ tokens[index_of_first_token(def_node, tokens) + 1].pos
+ end
+ end
+
+ def do_not_check_singleton_operator_defs(tokens, sexp,
+ positions_not_to_check)
+ # def self.===(other)
+ # ^
+ on_node(:defs, sexp) do |defs_node|
+ _receiver, name, _args = *defs_node
+ ix = index_of_first_token(defs_node, tokens)
+ name_token = tokens[ix..-1].find { |t| t.text == name.to_s }
+ positions_not_to_check << name_token.pos
+ end
+ end
+
+ def check_missing_space(token_before, token, token_after)
+ unless has_space?(token_before, token, token_after)
+ text = token.text.to_s + (token.type == :tOP_ASGN ? '=' : '')
+ add_offence(:convention, token.pos.line, MSG_MISSING % text)
+ end
+ end
+
+ def has_space?(token_before, token, token_after)
+ space_between?(token_before, token) && space_between?(token,
+ token_after)
+ end
end
class SpaceAroundBraces < Cop
include SurroundingSpace
+ MSG_LEFT = "Surrounding space missing for '{'."
+ MSG_RIGHT = "Space missing to the left of '}'."
- def check_unwanted_space(tokens, ix)
+ def inspect(source, tokens, sexp, comments)
+ @source = source
+ positions_not_to_check = get_positions_not_to_check(tokens, sexp)
+ tokens.each_cons(2) do |t1, t2|
+ next if ([t1.pos, t2.pos] - positions_not_to_check).size < 2
+
+ type1, type2 = t1.type, t2.type
+ # :tLBRACE in hash literals, :tLCURLY otherwise.
+ next if [:tLCURLY, :tLBRACE].include?(type1) && type2 == :tRCURLY
+ check(t1, t2, MSG_LEFT) if type1 == :tLCURLY || type2 == :tLCURLY
+ check(t1, t2, MSG_RIGHT) if type2 == :tRCURLY
+ end
end
- def check_missing_space(tokens, ix, grammar_path)
- # Checked by SpaceInsideHashLiteralBraces and not here.
- return if grammar_path.last == :hash
+ def get_positions_not_to_check(tokens, sexp)
+ positions_not_to_check = []
- t = tokens[ix]
- case t.type
- when :on_lbrace
- unless surrounded_by_whitespace?(tokens[ix - 1, 3])
- add_offence(:convention, t.pos.lineno,
- "Surrounding space missing for '{'.")
+ on_node(:hash, sexp) do |hash|
+ b_ix = index_of_first_token(hash, tokens)
+ e_ix = index_of_last_token(hash, tokens)
+ positions_not_to_check << tokens[b_ix].pos << tokens[e_ix].pos
+ end
+
+ # TODO: Check braces inside string/symbol/regexp/xstr interpolation.
+ on_node([:dstr, :dsym, :regexp, :xstr], sexp) do |s|
+ b_ix = index_of_first_token(s, tokens)
+ e_ix = index_of_last_token(s, tokens)
+ tokens[b_ix..e_ix].each do |t|
+ positions_not_to_check << t.pos if t.type == :tRCURLY
end
- when :on_rbrace
- unless whitespace?(tokens[ix - 1])
- add_offence(:convention, t.pos.lineno,
- "Space missing to the left of '}'.")
- end
end
+
+ positions_not_to_check
end
+
+ def check(t1, t2, msg)
+ unless space_between?(t1, t2)
+ add_offence(:convention, t1.pos.line, msg)
+ end
+ end
end
module SpaceInside
include SurroundingSpace
+ MSG = 'Space inside %s detected.'
- Paren = Struct.new :left, :right, :kind
-
- def check_unwanted_space(tokens, ix)
- paren = get_paren
- prev, t, nxt = tokens.values_at(ix - 1, ix, ix + 1)
- offence_detected = case t.type
- when paren.left
- nxt.type == :on_sp
- when paren.right
- if prev.type == :on_sp
- prev_ns = previous_non_space(tokens, ix)
- prev_ns &&
- prev_ns.pos.lineno == tokens[ix].pos.lineno &&
- # Avoid double reporting
- prev_ns.type != paren.left
- end
- end
- if offence_detected
- add_offence(:convention, t.pos.lineno,
- "Space inside #{paren.kind} detected.")
+ def inspect(source, tokens, sexp, comments)
+ @source = source
+ left, right, kind = specifics
+ tokens.each_cons(2) do |t1, t2|
+ if t1.type == left || t2.type == right
+ if t2.pos.line == t1.pos.line && space_between?(t1, t2)
+ add_offence(:convention, t1.pos.line, MSG % kind)
+ end
+ end
end
end
end
class SpaceInsideParens < Cop
include SpaceInside
- def get_paren
- Paren.new(:on_lparen, :on_rparen, 'parentheses')
+
+ def specifics
+ [:tLPAREN2, :tRPAREN, 'parentheses']
end
end
class SpaceInsideBrackets < Cop
include SpaceInside
- def get_paren
- Paren.new(:on_lbracket, :on_rbracket, 'square brackets')
+
+ def specifics
+ [:tLBRACK, :tRBRACK, 'square brackets']
end
end
class SpaceInsideHashLiteralBraces < Cop
include SurroundingSpace
+ MSG = 'Space inside hash literal braces %s.'
- def check_missing_space(tokens, ix, grammar_path)
- if self.class.config['EnforcedStyleIsWithSpaces']
- check_space(tokens, ix, grammar_path, 'missing') do |t|
- !(whitespace?(t) || [:on_lbrace, :on_rbrace].include?(t.type))
- end
+ def inspect(source, tokens, sexp, comments)
+ @source = source
+ on_node(:hash, sexp) do |hash|
+ b_ix = index_of_first_token(hash, tokens)
+ e_ix = index_of_last_token(hash, tokens)
+ check(tokens[b_ix], tokens[b_ix + 1])
+ check(tokens[e_ix - 1], tokens[e_ix])
end
end
- def check_unwanted_space(tokens, ix)
- unless self.class.config['EnforcedStyleIsWithSpaces']
- grammar_path = @correlations[ix] or return
- check_space(tokens, ix, grammar_path, 'detected') do |t|
- whitespace?(t)
- end
- end
+ def check(t1, t2)
+ types = [t1, t2].map(&:type)
+ braces = [:tLBRACE, :tRCURLY]
+ return if types == braces || (braces - types).size == 2
+ has_space = space_between?(t1, t2)
+ is_offence, word = if self.class.config['EnforcedStyleIsWithSpaces']
+ [!has_space, 'missing']
+ else
+ [has_space, 'detected']
+ end
+ add_offence(:convention, t1.pos.line, MSG % word) if is_offence
end
-
- private
-
- def check_space(tokens, ix, grammar_path, word)
- if grammar_path[-1] == :hash
- is_offence = case tokens[ix].type
- when :on_lbrace then yield tokens[ix + 1]
- when :on_rbrace then yield tokens[ix - 1]
- else false
- end
- if is_offence
- add_offence(:convention, tokens[ix].pos.lineno,
- "Space inside hash literal braces #{word}.")
- end
- end
- end
end
class SpaceAroundEqualsInParameterDefault < Cop
- def inspect(file, source, tokens, sexp)
- each(:params, sexp) do |s|
- (s[2] || []).each do |param, _|
- param_pos = param.last
- ix = tokens.index { |t| t.pos == param_pos }
- unless whitespace?(tokens[ix + 1]) && whitespace?(tokens[ix + 3])
- add_offence(:convention, param[-1].lineno,
- 'Surrounding space missing in default value ' +
- 'assignment.')
- end
+ include SurroundingSpace
+ MSG = 'Surrounding space missing in default value assignment.'
+
+ def inspect(source, tokens, sexp, comments)
+ @source = source
+ on_node(:optarg, sexp) do |optarg|
+ arg, equals, value = tokens[index_of_first_token(optarg, tokens), 3]
+ unless space_between?(arg, equals) && space_between?(equals, value)
+ add_offence(:convention, equals.pos.line, MSG)
end
end
end
end
end