class SCSSLint::ControlCommentProcessor
Tracks which lines have been disabled for a given linter.
Public Class Methods
# File lib/scss_lint/control_comment_processor.rb, line 6 def initialize(linter) @disable_stack = [] @disabled_lines = Set.new @linter = linter end
Public Instance Methods
Executed after a node has been visited.
@param node [Sass::Tree::Node]
# File lib/scss_lint/control_comment_processor.rb, line 43 def after_node_visit(node) while @disable_stack.any? && @disable_stack.last[:node].node_parent == node pop_control_comment_stack(node) end end
Executed before a node has been visited.
@param node [Sass::Tree::Node]
# File lib/scss_lint/control_comment_processor.rb, line 22 def before_node_visit(node) return unless (commands = Array(extract_commands(node))).any? commands.each do |command| linters = command[:linters] next unless linters.include?('all') || linters.include?(@linter.name) process_command(command, node) # Is the control comment the only thing on this line? next if node.is_a?(Sass::Tree::RuleNode) || %r{^\s*(//|/\*)}.match(@linter.engine.lines[command[:line] - 1]) # Otherwise, pop since we only want comment to apply to the single line pop_control_comment_stack(node) end end
Filter lints given the comments that were processed in the document.
@param lints [Array<SCSSLint::Lint>]
# File lib/scss_lint/control_comment_processor.rb, line 15 def filter_lints(lints) lints.reject { |lint| @disabled_lines.include?(lint.location.line) } end
Private Instance Methods
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).
# File lib/scss_lint/control_comment_processor.rb, line 114 def end_line(node) child = node prev_child = node until [nil, prev_child].include?(child = last_child(child)) prev_child = child end # Fall back to prev_child if last_child() returned nil (i.e. node had no # children with line numbers) (child || prev_child).line end
# File lib/scss_lint/control_comment_processor.rb, line 51 def extract_commands(node) return unless comment = retrieve_comment_text(node) commands = [] comment.split("\n").each_with_index do |comment_line, line_no| next unless match = %r{ //\s*scss-lint: (?<action>disable|enable)\s+ (?<linters>.*?) \s*($|\*\/) # End of line }x.match(comment_line) commands << { action: match[:action], linters: match[:linters].split(/\s*,\s*|\s+/), line: node.line + line_no } end commands 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.
Returns `nil` if node has no children or no children with associated line numbers.
@param node [Sass::Tree::Node, Sass::Script::Tree::Node] @return [Sass::Tree::Node, Sass::Script::Tree::Node]
# File lib/scss_lint/control_comment_processor.rb, line 137 def last_child(node) last = node.children.inject(node) do |lowest, child| return lowest unless child.respond_to?(:line) lowest.line < child.line ? child : lowest end # In this case, none of the children have associated line numbers or the # node has no children at all, so return `nil`. return if last == node last end
# File lib/scss_lint/control_comment_processor.rb, line 94 def pop_control_comment_stack(node) return unless command = @disable_stack.pop comment_node = command[:node] start_line = command[:line] end_line = if comment_node.class.node_name == :rule start_line elsif node.class.node_name == :root @linter.engine.lines.length else end_line(node) end @disabled_lines.merge(start_line..end_line) end
# File lib/scss_lint/control_comment_processor.rb, line 85 def process_command(command, node) case command[:action] when 'disable' @disable_stack << { node: node, line: command[:line] } when 'enable' pop_control_comment_stack(node) end end
# File lib/scss_lint/control_comment_processor.rb, line 73 def retrieve_comment_text(node) text_with_markers = case node when Sass::Tree::CommentNode node.value.first when Sass::Tree::RuleNode node.rule.select { |chunk| chunk.is_a?(String) }.join end text_with_markers.gsub(%r{\A/\*}, '//').gsub(/\n \*/, "\n//") if text_with_markers end