class SCSSLint::Config

Loads and manages application configuration.

Constants

DEFAULT_FILE
FILE_NAME
SEVERITIES

Attributes

file[R]
options[R]
warnings[R]

Public Class Methods

default() click to toggle source
# File lib/scss_lint/config.rb, line 14
def default
  load(DEFAULT_FILE, merge_with_default: false)
end
linter_name(linter) click to toggle source
# File lib/scss_lint/config.rb, line 45
def linter_name(linter)
  (linter.is_a?(Class) ? linter : linter.class).simple_name
end
load(file, options = {}) click to toggle source

Loads a configuration from a file, merging it with the default configuration.

# File lib/scss_lint/config.rb, line 20
def load(file, options = {})
  config_options = load_options_hash_from_file(file)

  config = new(config_options, file)

  # Need to call this before merging with the default configuration so
  # that plugins can override the default configuration while still being
  # overridden by the repo's configuration.
  config.load_plugins

  if options.fetch(:merge_with_default, true)
    config = default.extend(config)
  end

  config
end
new(options, file = Config.user_file) click to toggle source
# File lib/scss_lint/config.rb, line 204
def initialize(options, file = Config.user_file)
  @options = options
  @warnings = []
  @file = file

  validate_linters
end
user_file() click to toggle source

Returns the location of the user-wide scss-lint configuration.

This needs to be a method instead of a constant so that we can change the user's home directory in tests.

# File lib/scss_lint/config.rb, line 41
def user_file
  File.join(Dir.home, FILE_NAME)
end

Private Class Methods

apply_options_to_matching_linters(class_name_glob, current_options, linter_options) click to toggle source
# File lib/scss_lint/config.rb, line 108
def apply_options_to_matching_linters(class_name_glob, current_options, linter_options)
  linter_names_matching_glob(class_name_glob).each do |linter_name|
    old_options = current_options['linters'].fetch(linter_name, {})
    current_options['linters'][linter_name] = smart_merge(old_options, linter_options)
  end
end
convert_single_options_to_arrays(options) click to toggle source

Convert any config options that accept a single value or an array to an array form so that merging works.

# File lib/scss_lint/config.rb, line 82
def convert_single_options_to_arrays(options)
  options = options.dup

  if options['exclude']
    # Ensure exclude is an array, since we allow user to specify a single
    # string.
    options['exclude'] = [options['exclude']].flatten
  end

  options
end
default_options_hash() click to toggle source
# File lib/scss_lint/config.rb, line 51
def default_options_hash
  @default_options_hash ||= load_options_hash_from_file(DEFAULT_FILE)
end
ensure_exclude_paths_are_absolute(options, original_file) click to toggle source

Ensure all excludes are absolute paths

# File lib/scss_lint/config.rb, line 136
def ensure_exclude_paths_are_absolute(options, original_file)
  options = options.dup

  if options['exclude']
    excludes = [options['exclude']].flatten

    options['exclude'] = excludes.map do |exclusion_glob|
      if exclusion_glob.start_with?('/')
        exclusion_glob
      else
        # Expand the path assuming it is relative to the config file itself
        File.expand_path(exclusion_glob, File.expand_path(File.dirname(original_file)))
      end
    end
  end

  options
end
ensure_linter_exclude_paths_are_absolute(options, original_file) click to toggle source
# File lib/scss_lint/config.rb, line 122
def ensure_linter_exclude_paths_are_absolute(options, original_file)
  options = options.dup

  options['linters'] ||= {}

  options['linters'].each_key do |linter_name|
    options['linters'][linter_name] =
      ensure_exclude_paths_are_absolute(options['linters'][linter_name], original_file)
  end

  options
end
ensure_severities_are_valid(options) click to toggle source
# File lib/scss_lint/config.rb, line 155
def ensure_severities_are_valid(options)
  unless severity_is_valid?(options)
    raise SCSSLint::Exceptions::InvalidConfiguration,
          'Global `severity` configuration option must be one of [' \
          "#{SEVERITIES.join(' | ')}]"
  end

  options['linters'].each do |linter_name, linter_options|
    next if severity_is_valid?(linter_options)

    raise SCSSLint::Exceptions::InvalidConfiguration,
          "#{linter_name} `severity` configuration option must be one " \
          "of [#{SEVERITIES.join(' | ')}]"
  end
end
linter_names_matching_glob(class_name_glob) click to toggle source
# File lib/scss_lint/config.rb, line 115
def linter_names_matching_glob(class_name_glob)
  class_name_regex = /#{class_name_glob.gsub('*', '[^:]+')}/

  LinterRegistry.linters.map { |linter_class| linter_name(linter_class) }
                .select { |linter_name| linter_name.match(class_name_regex) }
end
load_file_contents(file) click to toggle source

For easy stubbing in tests

# File lib/scss_lint/config.rb, line 187
def load_file_contents(file)
  File.open(file, 'r').read
end
load_options_hash_from_file(file) click to toggle source

Recursively load config files, fetching files specified by `include` directives and merging the file's config with the files specified.

# File lib/scss_lint/config.rb, line 57
def load_options_hash_from_file(file)
  file_contents = load_file_contents(file)

  begin
    options =
      if yaml = YAML.load(file_contents)
        yaml.to_hash
      else
        {}
      end
  rescue StandardError => e
    raise SCSSLint::Exceptions::InvalidConfiguration,
          "Invalid configuration: #{e.message}"
  end

  options = convert_single_options_to_arrays(options)
  options = merge_wildcard_linter_options(options)
  options = ensure_exclude_paths_are_absolute(options, file)
  options = ensure_linter_exclude_paths_are_absolute(options, file)
  ensure_severities_are_valid(options)
  options
end
merge_wildcard_linter_options(options) click to toggle source

Merge options from wildcard linters into individual linter configs

# File lib/scss_lint/config.rb, line 95
def merge_wildcard_linter_options(options)
  options = options.dup
  # Cannot use `each_key` because the cycle adds new keys during iteration
  options.fetch('linters', {}).keys.each do |class_name|
    next unless class_name.include?('*')

    wildcard_options = options['linters'].delete(class_name)
    apply_options_to_matching_linters(class_name, options, wildcard_options)
  end

  options
end
path_relative_to_config(relative_include_path, base_config_path) click to toggle source
# File lib/scss_lint/config.rb, line 176
def path_relative_to_config(relative_include_path, base_config_path)
  if relative_include_path.start_with?('/')
    relative_include_path
  else
    path = File.join(File.dirname(base_config_path), relative_include_path)
    # Remove double backslashes appearing in Windows paths.
    path.sub(%r{^//}, File::SEPARATOR)
  end
end
severity_is_valid?(options) click to toggle source
# File lib/scss_lint/config.rb, line 172
def severity_is_valid?(options)
  SEVERITIES.include?(options.fetch('severity', 'warning'))
end
smart_merge(parent, child) click to toggle source

Merge two hashes, concatenating lists and further merging nested hashes.

# File lib/scss_lint/config.rb, line 192
def smart_merge(parent, child)
  parent.merge(child) do |_key, old, new|
    case old
    when Hash
      smart_merge(old, new)
    else
      new
    end
  end
end

Public Instance Methods

==(other) click to toggle source

Compares this configuration with another.

@param other [SCSSLint::Config] @return [true,false]

Calls superclass method
# File lib/scss_lint/config.rb, line 220
def ==(other)
  super || @options == other.options
end
[](key) click to toggle source
# File lib/scss_lint/config.rb, line 212
def [](key)
  @options[key]
end
disable_all_linters() click to toggle source
# File lib/scss_lint/config.rb, line 266
def disable_all_linters
  @options['linters'].each_value do |linter_config|
    linter_config['enabled'] = false
  end
end
disable_linter(linter) click to toggle source
# File lib/scss_lint/config.rb, line 262
def disable_linter(linter)
  linter_options(linter)['enabled'] = false
end
enable_linter(linter) click to toggle source
# File lib/scss_lint/config.rb, line 258
def enable_linter(linter)
  linter_options(linter)['enabled'] = true
end
enabled_linters() click to toggle source
# File lib/scss_lint/config.rb, line 248
def enabled_linters
  LinterRegistry.extract_linters_from(@options['linters'].keys).select do |linter|
    linter_options(linter)['enabled']
  end
end
exclude_file(file_path) click to toggle source
# File lib/scss_lint/config.rb, line 298
def exclude_file(file_path)
  abs_path = File.expand_path(file_path)

  @options['exclude'] ||= []
  @options['exclude'] << abs_path
end
exclude_patterns() click to toggle source
# File lib/scss_lint/config.rb, line 286
def exclude_patterns
  @options.fetch('exclude', [])
end
excluded_file?(file_path) click to toggle source
# File lib/scss_lint/config.rb, line 278
def excluded_file?(file_path)
  abs_path = File.expand_path(file_path)

  @options.fetch('exclude', []).any? do |exclusion_glob|
    File.fnmatch(exclusion_glob, abs_path)
  end
end
excluded_file_for_linter?(file_path, linter) click to toggle source
# File lib/scss_lint/config.rb, line 290
def excluded_file_for_linter?(file_path, linter)
  abs_path = File.expand_path(file_path)

  linter_options(linter).fetch('exclude', []).any? do |exclusion_glob|
    File.fnmatch(exclusion_glob, abs_path)
  end
end
extend(config) click to toggle source

Extend this {Config} with another configuration.

@return [SCSSLint::Config]

# File lib/scss_lint/config.rb, line 227
def extend(config)
  @options = self.class.send(:smart_merge, @options, config.options)
  @warnings += config.warnings
  self
end
linter_enabled?(linter) click to toggle source
# File lib/scss_lint/config.rb, line 254
def linter_enabled?(linter)
  (linter_options(linter) || {}).fetch('enabled', false)
end
linter_options(linter) click to toggle source
# File lib/scss_lint/config.rb, line 272
def linter_options(linter)
  options = @options['linters'].fetch(self.class.linter_name(linter), {})
  options['severity'] ||= @options['severity']
  options
end
load_plugins() click to toggle source
# File lib/scss_lint/config.rb, line 233
def load_plugins
  previous_linters = LinterRegistry.linters
  plugins = SCSSLint::Plugins.new(self).load
  new_linters = LinterRegistry.linters - previous_linters

  plugins.each do |plugin|
    # Have the plugin options be overrideable by the local configuration
    @options = self.class.send(:smart_merge, plugin.config.options, @options)
  end

  # We only want to set defaults for linters introduced via plugins,
  # otherwise we'll accidentally enable some linters
  ensure_linters_have_default_options(new_linters)
end
scss_files() click to toggle source

@return Array

# File lib/scss_lint/config.rb, line 306
def scss_files
  if (path = @options['scss_files']) && Array(path).any?
    Array(path).map { |p| Dir[p] }.flatten.uniq
  else
    []
  end
end

Private Instance Methods

default_plugin_options(linter) click to toggle source
# File lib/scss_lint/config.rb, line 336
def default_plugin_options(linter)
  { self.class.linter_name(linter) => { 'enabled' => true } }
end
ensure_linters_have_default_options(linters) click to toggle source
# File lib/scss_lint/config.rb, line 328
def ensure_linters_have_default_options(linters)
  linters.each do |linter|
    if linter_options(linter).nil?
      @options['linters'].merge!(default_plugin_options(linter))
    end
  end
end
validate_linters() click to toggle source
# File lib/scss_lint/config.rb, line 316
def validate_linters
  return unless linters = @options['linters']

  linters.each_key do |name|
    begin
      Linter.const_get(name)
    rescue NameError
      @warnings << "Linter #{name} does not exist; ignoring"
    end
  end
end