class SCSSLint::Linter::Shorthand

Checks for the use of the shortest form for properties that can be written in shorthand.

Constants

LIST_LITERAL_REGEX
SHORTHANDABLE_PROPERTIES

Public Instance Methods

visit_prop(node) click to toggle source

@param node [Sass::Tree::Node]

# File lib/scss_lint/linter/shorthand.rb, line 13
def visit_prop(node)
  property_name = node.name.join
  return unless SHORTHANDABLE_PROPERTIES.include?(property_name)

  if @shorthands_forbidden
    add_lint(node, "The `#{property_name}` shorthand property is " \
                   'forbidden since the `allowed_shorthands` option ' \
                   'is set to an empty list.')

  end

  case node.value.first
  when Sass::Script::Tree::Literal
    check_script_literal(property_name, node.value.first)
  when Sass::Script::Tree::ListLiteral
    check_script_list(property_name, node.value.first)
  end
end
visit_root(*) { || ... } click to toggle source
# File lib/scss_lint/linter/shorthand.rb, line 7
def visit_root(*)
  @shorthands_forbidden = @config['allowed_shorthands'] == []
  yield # Continue linting children
end

Private Instance Methods

allowed?(size) click to toggle source

@param size [Number] @return [Boolean]

# File lib/scss_lint/linter/shorthand.rb, line 152
def allowed?(size)
  return false unless config['allowed_shorthands']
  config['allowed_shorthands'].map(&:to_i).include?(size)
end
check_script_list(prop, list) click to toggle source

@param prop [String] @param list [Sass::Script::Tree::ListLiteral]

# File lib/scss_lint/linter/shorthand.rb, line 45
def check_script_list(prop, list)
  check_shorthand(prop, list, list.children.map(&:to_sass))
end
check_script_literal(prop, literal) click to toggle source

@param prop [String] @param literal [Sass::Script::Tree::Literal]

# File lib/scss_lint/linter/shorthand.rb, line 51
def check_script_literal(prop, literal)
  value = literal.value

  # HACK: node_parent may not be initialized at this point, so we need to
  # set it ourselves
  value.node_parent = literal
  return unless value.is_a?(Sass::Script::Value::String)

  check_script_string(prop, value)
end
check_script_string(prop, script_string) click to toggle source

@param prop [String] @param script_string [Sass::Script::Value::String]

# File lib/scss_lint/linter/shorthand.rb, line 71
def check_script_string(prop, script_string)
  return unless script_string.type == :identifier
  return unless values = script_string.value.strip[LIST_LITERAL_REGEX, 1]

  check_shorthand(prop, script_string, values.split)
end
check_shorthand(prop, node, values) click to toggle source

@param prop [String] @param node [Sass::Script::Value::String] @param values [Array<String>]

# File lib/scss_lint/linter/shorthand.rb, line 81
def check_shorthand(prop, node, values)
  values = shorthand_values(values)

  unless allowed?(values.count)
    add_lint(node, "Shorthands of length `#{values.count}` are not allowed. " \
                   "Value was `#{values.join(' ')}`")
  end

  return unless (2..4).member?(values.count)

  shortest_form = condensed_shorthand(*values)
  return if values == shortest_form

  add_lint(node, "Shorthand form for property `#{prop}` should be " \
                 "written more concisely as `#{shortest_form.join(' ')}` " \
                 "instead of `#{values.join(' ')}`")
end
condense_to_one_value?(top, right, bottom, left) click to toggle source

@param top [String] @param right [String] @param bottom [String] @param left [String] @return [Boolean]

# File lib/scss_lint/linter/shorthand.rb, line 121
def condense_to_one_value?(top, right, bottom, left) # rubocop:disable Metrics/CyclomaticComplexity
  return unless allowed?(1)
  return unless top == right

  top == bottom && (bottom == left || left.nil?) ||
    bottom.nil? && left.nil?
end
condense_to_three_values?(_, right, _, left) click to toggle source

@param right [String] @param left [String] @return [Boolean]

# File lib/scss_lint/linter/shorthand.rb, line 144
def condense_to_three_values?(_, right, _, left)
  return unless allowed?(3)

  right == left
end
condense_to_two_values?(top, right, bottom, left) click to toggle source

@param top [String] @param right [String] @param bottom [String] @param left [String] @return [Boolean]

# File lib/scss_lint/linter/shorthand.rb, line 134
def condense_to_two_values?(top, right, bottom, left)
  return unless allowed?(2)

  top == bottom && right == left ||
    top == bottom && left.nil? && top != right
end
condensed_shorthand(top, right, bottom = nil, left = nil) click to toggle source

@param top [String] @param right [String] @param bottom [String] @param left [String] @return [Array]

# File lib/scss_lint/linter/shorthand.rb, line 104
def condensed_shorthand(top, right, bottom = nil, left = nil)
  if condense_to_one_value?(top, right, bottom, left)
    [top]
  elsif condense_to_two_values?(top, right, bottom, left)
    [top, right]
  elsif condense_to_three_values?(top, right, bottom, left)
    [top, right, bottom]
  else
    [top, right, bottom, left].compact
  end
end
shorthand_values(values) click to toggle source
# File lib/scss_lint/linter/shorthand.rb, line 157
def shorthand_values(values)
  values.take(4).take_while { |value| !value.to_s.start_with?('!') }
end