All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gems.scss-lint-0.34.0.lib.scss_lint.control_comment_processor.rb Maven / Gradle / Ivy

There is a newer version: 3.7.2
Show newest version
require 'set'

module SCSSLint
  # Tracks which lines have been disabled for a given linter.
  class ControlCommentProcessor
    def initialize(linter)
      @disable_stack = []
      @disabled_lines = Set.new
      @linter = linter
    end

    # Filter lints given the comments that were processed in the document.
    #
    # @param lints [Array]
    def filter_lints(lints)
      lints.reject { |lint| @disabled_lines.include?(lint.location.line) }
    end

    # Executed before a node has been visited.
    #
    # @param node [Sass::Tree::Node]
    def before_node_visit(node)
      return unless command = extract_command(node)

      linters = command[:linters]
      return unless linters.include?('all') || linters.include?(@linter.name)

      process_command(command[:action], node)

      # Is the control comment the only thing on this line?
      return if node.is_a?(Sass::Tree::RuleNode) ||
                %r{^\s*(//|/\*)}.match(@linter.engine.lines[node.line - 1])

      # Otherwise, pop since we only want comment to apply to the single line
      pop_control_comment_stack(node)
    end

    # Executed after a node has been visited.
    #
    # @param node [Sass::Tree::Node]
    def after_node_visit(node)
      while @disable_stack.any? && @disable_stack.last.node_parent == node
        pop_control_comment_stack(node)
      end
    end

  private

    def extract_command(node)
      comment =
        case node
        when Sass::Tree::CommentNode
          node.value.first
        when Sass::Tree::RuleNode
          node.rule.select { |chunk| chunk.is_a?(String) }.join
        end

      return unless match = %r{
        (/|\*)\s* # Comment start marker
        scss-lint:
        (?disable|enable)\s+
        (?.*?)
        \s*(?:\*/|\n) # Comment end marker or end of line
      }x.match(comment)

      {
        action: match[:action],
        linters: match[:linters].split(/\s*,\s*|\s+/),
      }
    end

    def process_command(command, node)
      case command
      when 'disable'
        @disable_stack << node
      when 'enable'
        pop_control_comment_stack(node)
      end
    end

    def pop_control_comment_stack(node)
      return unless comment_node = @disable_stack.pop

      start_line = comment_node.line

      # Find the deepest child that has a line number to which a lint might
      # apply (if it is a control comment enable node, it will be the line of
      # the comment itself).
      child = node
      prev_child = node
      while (child = last_child(child)) != prev_child
        prev_child = child
      end

      end_line = child.line

      @disabled_lines.merge(start_line..end_line)
    end

    # Gets the child of the node that resides on the lowest line in the file.
    #
    # This is necessary due to the fact that our monkey patching of the parse
    # tree's {#children} method does not return nodes sorted by their line
    # number.
    #
    # @param node [Sass::Tree::Node, Sass::Script::Tree::Node]
    # @return [Sass::Tree::Node, Sass::Script::Tree::Node]
    def last_child(node)
      node.children.inject(node) do |lowest, child|
        return lowest unless child.respond_to?(:line)
        lowest.line < child.line ? child : lowest
      end
    end
  end
end




© 2015 - 2025 Weber Informatics LLC | Privacy Policy