class SCSSLint::Linter::SpaceBetweenParens

Checks for the presence of spaces between parentheses.

Constants

TRAILING_WHITESPACE

Public Instance Methods

check_node(node) { || ... } click to toggle source
# File lib/scss_lint/linter/space_between_parens.rb, line 8
def check_node(node)
  check(node, source_from_range(node.source_range))
  yield
end
feel_for_parens_and_check_node(node) { || ... } click to toggle source
# File lib/scss_lint/linter/space_between_parens.rb, line 21
def feel_for_parens_and_check_node(node)
  source = feel_for_enclosing_parens(node)
  check(node, source)
  yield
end
visit_atroot(node)
Alias for: check_node
visit_cssimport(node)
Alias for: check_node
visit_function(node)
Alias for: check_node
visit_media(node)
Alias for: check_node
visit_mixin(node)
Alias for: check_node
visit_mixindef(node)
Alias for: check_node
visit_script_funcall(node)
Alias for: check_node
visit_script_listliteral(node)
visit_script_mapliteral(node)
visit_script_operation(node)
visit_script_string(node)

Private Instance Methods

check(node, source) click to toggle source
# File lib/scss_lint/linter/space_between_parens.rb, line 36
def check(node, source) # rubocop:disable Metrics/MethodLength
  @spaces = config['spaces']
  source = trim_right_paren(source)
  return if source.count('(') != source.count(')')
  source.scan(/
      \(
      (?<left>\s*)
      (?<contents>.*)
      (?<right>\s*)
      \)
  /x) do |left, contents, right|
    right = contents.match(TRAILING_WHITESPACE)[0] + right
    contents.gsub(TRAILING_WHITESPACE, '')

    # We don't lint on multiline parenthetical source.
    break if (left + contents + right).include? "\n"

    if contents.empty?
      # If we're looking at empty parens (like `()`, `( )`, `(  )`, etc.),
      # only report a possible lint on the left side.
      right = ' ' * @spaces
    end

    if left != ' ' * @spaces
      message = "#{expected_spaces} after `(` instead of `#{left}`"
      add_lint(node, message)
    end

    if right != ' ' * @spaces
      message = "#{expected_spaces} before `)` instead of `#{right}`"
      add_lint(node, message)
    end
  end
end
expected_spaces() click to toggle source
# File lib/scss_lint/linter/space_between_parens.rb, line 108
def expected_spaces
  "Expected #{pluralize(@spaces, 'space')}"
end
feel_for_enclosing_parens(node) click to toggle source

An expression enclosed in parens will include or not include each paren, depending on whitespace. Here we feel out for enclosing parens, and return them as the new source for the node.

# File lib/scss_lint/linter/space_between_parens.rb, line 74
def feel_for_enclosing_parens(node) # rubocop:disable Metrics/CyclomaticComplexity
  range = node.source_range
  original_source = source_from_range(range)
  left_offset = -1
  right_offset = 0

  if original_source[-1] != ')'
    right_offset += 1 while character_at(range.end_pos, right_offset) =~ /\s/

    return original_source if character_at(range.end_pos, right_offset) != ')'
  end

  # At this point, we know that we're wrapped on the right by a ')'.
  # Are we wrapped on the left by a '('?
  left_offset -= 1 while character_at(range.start_pos, left_offset) =~ /\s/
  return original_source if character_at(range.start_pos, left_offset) != '('

  # At this point, we know we're wrapped on both sides by parens. However,
  # those parens may be part of a parent function call. We don't care about
  # such parens. This depends on whether the preceding character is part of
  # a function name.
  return original_source if character_at(range.start_pos, left_offset - 1) =~ /[A-Za-z0-9_]/

  range.start_pos.offset += left_offset
  range.end_pos.offset += right_offset
  source_from_range(range)
end
trim_right_paren(source) click to toggle source

An unrelated right paren will sneak into the source of a node if there is no whitespace between the node and the right paren.

# File lib/scss_lint/linter/space_between_parens.rb, line 104
def trim_right_paren(source)
  (source.count(')') == source.count('(') + 1) ? source[0..-2] : source
end