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 19
def initialize(config)
  @config = config
  @lints = []
end
ruby_parser() click to toggle source
# File lib/haml_lint/linter.rb, line 118
def self.ruby_parser # rubocop:disable Lint/IneffectiveAccessModifier
  @ruby_parser ||= HamlLint::RubyParser.new
end
supports_autocorrect(value) click to toggle source

Linters can call supports_autocorrect(true) in their top-level scope to indicate that they supports autocorrect.

@params value [Boolean] The new value for supports_autocorrect

# File lib/haml_lint/linter.rb, line 96
                     def self.supports_autocorrect(value)
  @supports_autocorrect = value
end
supports_autocorrect?() click to toggle source

Returns true if this linter supports autocorrect, false otherwise

@return [Boolean]

# File lib/haml_lint/linter.rb, line 80
def self.supports_autocorrect?
  @supports_autocorrect || false
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 73
def name
  self.class.name.to_s.split('::').last
end
run(document, autocorrect: nil) click to toggle source

Runs the linter against the given Haml document.

@param document [HamlLint::Document]

# File lib/haml_lint/linter.rb, line 27
def run(document, autocorrect: nil) # rubocop:disable Metrics
  run_or_raise(document, autocorrect: autocorrect)
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
    )
rescue StandardError => e
  msg = +"Couldn't process the file"
  if @autocorrect
    # Those lints related to auto-correction were not saved, so don't display them
    @lints = []
    msg << " for autocorrect '#{@autocorrect}'. "
  else
    msg << ' for linting. '
  end

  msg << "#{e.class.name}: #{e.message}"
  if ENV['HAML_LINT_DEBUG'] == 'true'
    msg << "(DEBUG: Backtrace follows)\n#{e.backtrace.join("\n")}\n------"
  end

  @lints << HamlLint::Lint.new(self, document.file, nil, msg, :error)
  @lints
end
run_or_raise(document, autocorrect: nil) click to toggle source

Runs the linter against the given Haml document, raises if the file cannot be processed due to Syntax or HAML-Lint internal errors. (For testing purposes)

@param document [HamlLint::Document]

# File lib/haml_lint/linter.rb, line 62
def run_or_raise(document, autocorrect: nil)
  @document = document
  @lints = []
  @autocorrect = autocorrect
  visit(document.tree)
  @lints
end
supports_autocorrect?() click to toggle source
# File lib/haml_lint/linter.rb, line 84
def supports_autocorrect?
  self.class.supports_autocorrect?
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 135
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 217
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 168
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 188
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 203
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 114
def parse_ruby(source)
  self.class.ruby_parser.parse(source)
end
record_lint(node_or_line, message, corrected: false) click to toggle source

Record a lint for reporting back to the user.

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

# File lib/haml_lint/linter.rb, line 104
def record_lint(node_or_line, message, corrected: false)
  line = node_or_line.is_a?(Integer) ? node_or_line : node_or_line.line
  @lints << HamlLint::Lint.new(self, @document.file, line, message,
                               config.fetch('severity', :warning).to_sym,
                               corrected: corrected)
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 127
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 145
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 229
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.delete_suffix('|').rstrip
  end.join(' ')
end