class HamlLint::Linter

Base implementation for all lint checks.

@abstract

Attributes

config[R]
document[R]
lints[R]

List of lints reported by this linter.

@todo Remove once spec/support/shared_linter_context returns an array of

lints for the subject instead of the linter itself.

Public Class Methods

new(config) click to toggle source

Initializes a linter with the specified configuration.

@param config [Hash] configuration for this linter

# File lib/haml_lint/linter.rb, line 18
def initialize(config)
  @config = config
  @lints = []
end

Public Instance Methods

name() click to toggle source

Returns the simple name for this linter.

@return [String]

# File lib/haml_lint/linter.rb, line 46
def name
  self.class.name.to_s.split('::').last
end
run(document) click to toggle source

Runs the linter against the given Haml document.

@param document [HamlLint::Document]

# File lib/haml_lint/linter.rb, line 26
def run(document)
  @document = document
  @lints = []
  visit(document.tree)
  @lints
rescue Parser::SyntaxError => e
  location = e.diagnostic.location
  @lints <<
    HamlLint::Lint.new(
      HamlLint::Linter::Syntax.new(config),
      document.file,
      location.line,
      e.to_s,
      :error
    )
end

Private Instance Methods

contains_interpolation?(string) click to toggle source

Returns whether a string contains any interpolation.

@param string [String] @return [true,false]

# File lib/haml_lint/linter.rb, line 84
def contains_interpolation?(string)
  return false unless string
  Haml::Util.contains_interpolation?(string)
end
following_node_line(node) click to toggle source

Returns the line of the “following node” (child of this node or sibling or the last line in the file).

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

# File lib/haml_lint/linter.rb, line 166
def following_node_line(node)
  [
    [node.children.first, next_node(node)].compact.map(&:line),
    @document.source_lines.count + 1,
  ].flatten.min
end
inline_content_is_string?(node) click to toggle source

Returns whether the inline content for a node is a string.

For example, the following node has a literal string:

%tag= "A literal #{string}"

whereas this one does not:

%tag A literal #{string}

@param node [HamlLint::Tree::Node] @return [true,false]

# File lib/haml_lint/linter.rb, line 117
def inline_content_is_string?(node)
  tag_with_inline_content = tag_with_inline_text(node)
  inline_content = inline_node_content(node)

  index = tag_with_inline_content.rindex(inline_content) - 1

  %w[' "].include?(tag_with_inline_content[index])
end
inline_node_content(node) click to toggle source

Get the inline content for this node.

Inline content is the content that appears inline right after the tag/script. For example, in the code below…

%tag Some inline content

…“Some inline content” would be the inline content.

@param node [HamlLint::Tree::Node] @return [String]

# File lib/haml_lint/linter.rb, line 137
def inline_node_content(node)
  inline_content = node.script

  if contains_interpolation?(inline_content)
    strip_surrounding_quotes(inline_content)
  else
    inline_content
  end
end
next_node(node) click to toggle source

Gets the next node following this node, ascending up the ancestor chain recursively if this node has no siblings.

@param node [HamlLint::Tree::Node] @return [HamlLint::Tree::Node,nil]

# File lib/haml_lint/linter.rb, line 152
def next_node(node)
  return unless node
  siblings = node.parent ? node.parent.children : [node]

  next_sibling = siblings[siblings.index(node) + 1] if siblings.count > 1
  return next_sibling if next_sibling

  next_node(node.parent)
end
parse_ruby(source) click to toggle source

Parse Ruby code into an abstract syntax tree.

@return [AST::Node]

# File lib/haml_lint/linter.rb, line 66
def parse_ruby(source)
  @ruby_parser ||= HamlLint::RubyParser.new
  @ruby_parser.parse(source)
end
record_lint(node, message) click to toggle source

Record a lint for reporting back to the user.

@param node [#line] node to extract the line number from @param message [String] error/warning to display to the user

# File lib/haml_lint/linter.rb, line 58
def record_lint(node, message)
  @lints << HamlLint::Lint.new(self, @document.file, node.line, message,
                               config.fetch('severity', :warning).to_sym)
end
strip_surrounding_quotes(string) click to toggle source

Remove the surrounding double quotes from a string, ignoring any leading/trailing whitespace.

@param string [String] @return [String] stripped with leading/trailing double quotes removed.

# File lib/haml_lint/linter.rb, line 76
def strip_surrounding_quotes(string)
  string[/\A\s*"(.*)"\s*\z/, 1]
end
tag_has_inline_script?(tag_node) click to toggle source

Returns whether this tag node has inline script, e.g. is of the form %tag= …

@param tag_node [HamlLint::Tree::TagNode] @return [true,false]

# File lib/haml_lint/linter.rb, line 94
def tag_has_inline_script?(tag_node)
  tag_with_inline_content = tag_with_inline_text(tag_node)
  return false unless inline_content = inline_node_content(tag_node)
  return false unless index = tag_with_inline_content.rindex(inline_content)

  index -= 1
  index -= 1 while [' ', '"', "'"].include?(tag_with_inline_content[index])

  tag_with_inline_content[index] == '='
end
tag_with_inline_text(tag_node) click to toggle source

Extracts all text for a tag node and normalizes it, including additional lines following commas or multiline bar indicators ('|')

@param tag_node [HamlLine::Tree::TagNode] @return [String] source code of original parse node

# File lib/haml_lint/linter.rb, line 178
def tag_with_inline_text(tag_node)
  # Normalize each of the lines to ignore the multiline bar (|) and
  # excess whitespace
  @document.source_lines[(tag_node.line - 1)...(following_node_line(tag_node) - 1)]
           .map do |line|
    line.strip.gsub(/\|\z/, '').rstrip
  end.join(' ')
end