class SCSSLint::Linter
Defines common functionality available to all linters.
Attributes
Public Class Methods
When defining a Linter
class, define its simple name as well. This assumes that the module hierarchy of every linter starts with `SCSSLint::Linter::`, and removes this part of the class name.
`SCSSLint::Linter::Foo.simple_name` #=> “Foo” `SCSSLint::Linter::Compass::Bar.simple_name` #=> “Compass::Bar”
# File lib/scss_lint/linter.rb, line 16 def inherited(linter) name_parts = linter.name.split('::') name = name_parts.length < 3 ? '' : name_parts[2..-1].join('::') linter.simple_name = name end
Create a linter.
# File lib/scss_lint/linter.rb, line 26 def initialize @lints = [] end
Public Instance Methods
Return the human-friendly name of this linter as specified in the configuration file and in lint descriptions.
# File lib/scss_lint/linter.rb, line 47 def name self.class.simple_name end
Run this linter against a parsed document with the given configuration, returning the lints that were found.
@param engine [Engine] @param config [Config] @return [Array<Lint>]
# File lib/scss_lint/linter.rb, line 36 def run(engine, config) @lints = [] @config = config @engine = engine @comment_processor = ControlCommentProcessor.new(self) visit(engine.tree) @lints = @comment_processor.filter_lints(@lints) end
Protected Instance Methods
Helper for creating lint from a parse tree node
@param node_or_line_or_location [Sass::Script::Tree::Node, Fixnum,
SCSSLint::Location, Sass::Source::Position]
@param message [String]
# File lib/scss_lint/linter.rb, line 58 def add_lint(node_or_line_or_location, message) @lints << Lint.new(self, engine.filename, extract_location(node_or_line_or_location), message, @config.fetch('severity', :warning).to_sym) end
Extract {SCSSLint::Location} from a {Sass::Source::Range}.
@param range [Sass::Source::Range] @return [SCSSLint::Location]
# File lib/scss_lint/linter.rb, line 70 def location_from_range(range) # rubocop:disable Metrics/AbcSize length = if range.start_pos.line == range.end_pos.line range.end_pos.offset - range.start_pos.offset else line_source = engine.lines[range.start_pos.line - 1] line_source.length - range.start_pos.offset + 1 end # Workaround for https://github.com/sds/scss-lint/issues/887 to acount for # https://github.com/sass/sass/issues/2284. length = 1 if length < 1 Location.new(range.start_pos.line, range.start_pos.offset, length) end
Returns whether a given node spans only a single line.
@param node [Sass::Tree::Node] @return [true,false] whether the node spans a single line
# File lib/scss_lint/linter.rb, line 118 def node_on_single_line?(node) return if node.source_range.start_pos.line != node.source_range.end_pos.line # The Sass parser reports an incorrect source range if the trailing curly # brace is on the next line, e.g. # # p { # } # # Since we don't want to count this as a single line node, check if the # last character on the first line is an opening curly brace. engine.lines[node.line - 1].strip[-1] != '{' end
Extracts the original source code given a range.
@param source_range [Sass::Source::Range] @return [String] the original source code
# File lib/scss_lint/linter.rb, line 89 def source_from_range(source_range) # rubocop:disable Metrics/AbcSize current_line = source_range.start_pos.line - 1 last_line = source_range.end_pos.line - 1 start_pos = source_range.start_pos.offset - 1 source = if current_line == last_line engine.lines[current_line][start_pos..(source_range.end_pos.offset - 1)] else engine.lines[current_line][start_pos..-1] end current_line += 1 while current_line < last_line source += engine.lines[current_line].to_s current_line += 1 end if source_range.start_pos.line != source_range.end_pos.line source += ((engine.lines[current_line] || '')[0...source_range.end_pos.offset]).to_s end source end
Modified so we can also visit selectors in linters
@param node [Sass::Tree::Node, Sass::Script::Tree::Node
,
Sass::Script::Value::Base]
# File lib/scss_lint/linter.rb, line 136 def visit(node) # Visit the selector of a rule if parsed rules are available if node.is_a?(Sass::Tree::RuleNode) && node.parsed_rules visit_selector(node.parsed_rules) end @comment_processor.before_node_visit(node) if @engine.any_control_commands super @comment_processor.after_node_visit(node) if @engine.any_control_commands end
Redefine so we can set the `node_parent` of each node
@param parent [Sass::Tree::Node, Sass::Script::Tree::Node
,
Sass::Script::Value::Base]
# File lib/scss_lint/linter.rb, line 151 def visit_children(parent) parent.children.each do |child| child.node_parent = parent visit(child) end end
Private Instance Methods
@param source_position [Sass::Source::Position] @param offset [Integer] @return [String] the character at the given [Sass::Source::Position]
# File lib/scss_lint/linter.rb, line 188 def character_at(source_position, offset = 0) actual_line = source_position.line - 1 actual_offset = source_position.offset + offset - 1 return nil if actual_offset < 0 engine.lines.size > actual_line && engine.lines[actual_line][actual_offset] end
# File lib/scss_lint/linter.rb, line 170 def extract_location(node_or_line_or_location) if node_or_line_or_location.is_a?(Location) node_or_line_or_location elsif node_or_line_or_location.is_a?(Sass::Source::Position) Location.new(node_or_line_or_location.line, node_or_line_or_location.offset) elsif node_or_line_or_location.respond_to?(:source_range) && node_or_line_or_location.source_range location_from_range(node_or_line_or_location.source_range) elsif node_or_line_or_location.respond_to?(:line) Location.new(node_or_line_or_location.line) else Location.new(node_or_line_or_location) end end
Starting at source_position (plus offset), search for pattern and return the offset from the source_position.
@param source_position [Sass::Source::Position] @param pattern [String, RegExp] the pattern to search for @param offset [Integer] @return [Integer] the offset at which [pattern] was found.
# File lib/scss_lint/linter.rb, line 204 def offset_to(source_position, pattern, offset = 0) actual_line = source_position.line - 1 actual_offset = source_position.offset + offset - 1 return nil if actual_line >= engine.lines.size actual_index = engine.lines[actual_line].index(pattern, actual_offset) actual_index && actual_index + 1 - source_position.offset end
# File lib/scss_lint/linter.rb, line 160 def visit_comment(_node) # Don't lint children of comments by default, as the Sass parser contains # many bugs related to the source ranges reported within code in /*...*/ # comments. # # Instead of defining this empty method on every linter, we assume every # linter ignores comments by default. Individual linters can override at # their discretion. end