Skip to content

Commit

Permalink
Incorporate slim-lint design back into haml-lint
Browse files Browse the repository at this point in the history
The `slim-lint` project was created using `haml-lint` as a template, but
then improved on it to make 100% code coverage possible.

Incorporate the changes back into `haml-lint`, since much of the
framework code is identical. This paves the way for us to consider
extracting the framework code into a separate gem.

This includes a bunch of specs so we can get `haml-lint` closer to 100%
code coverage.
  • Loading branch information
sds committed Jun 8, 2015
1 parent 54ac3b6 commit f222bfa
Show file tree
Hide file tree
Showing 51 changed files with 1,331 additions and 535 deletions.
1 change: 1 addition & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ linters:
- Style/AlignParameters
- Style/BlockNesting
- Style/FileName
- Style/FinalNewline
- Style/IfUnlessModifier
- Style/IndentationWidth
- Style/Next
Expand Down
3 changes: 2 additions & 1 deletion lib/haml_lint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
require 'haml_lint/exceptions'
require 'haml_lint/configuration'
require 'haml_lint/configuration_loader'
require 'haml_lint/parser'
require 'haml_lint/document'
require 'haml_lint/haml_visitor'
require 'haml_lint/lint'
require 'haml_lint/linter_registry'
Expand All @@ -11,6 +11,7 @@
require 'haml_lint/logger'
require 'haml_lint/reporter'
require 'haml_lint/report'
require 'haml_lint/linter_selector'
require 'haml_lint/file_finder'
require 'haml_lint/runner'
require 'haml_lint/utils'
Expand Down
33 changes: 25 additions & 8 deletions lib/haml_lint/cli.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
require 'haml_lint'
require 'haml_lint/options'

require 'sysexits'

module HamlLint
# Command line application interface.
class CLI
attr_accessor :options

# Create a CLI that outputs to the specified logger.
#
# @param logger [HamlLint::Logger]
def initialize(logger)
@log = logger
Expand All @@ -16,7 +17,7 @@ def initialize(logger)
# based on those arguments.
#
# @param args [Array<String>] command line arguments
# @return [Fixnum] exit status returned by the application
# @return [Integer] exit status code
def run(args)
options = HamlLint::Options.new.parse(args)
act_on_options(options)
Expand All @@ -28,6 +29,9 @@ def run(args)

attr_reader :log

# Given the provided options, execute the appropriate command.
#
# @return [Integer] exit status code
def act_on_options(options)
log.color_enabled = options.fetch(:color, log.tty?)

Expand All @@ -48,6 +52,8 @@ def act_on_options(options)
end
end

# Outputs a message and returns an appropriate error code for the specified
# exception.
def handle_exception(ex)
case ex
when HamlLint::Exceptions::ConfigurationError
Expand All @@ -69,21 +75,27 @@ def handle_exception(ex)
end
end

# Scans the files specified by the given options for lints.
#
# @return [Integer] exit status code
def scan_for_lints(options)
report = Runner.new.run(options)
print_report(report, options)
report.failed? ? Sysexits::EX_DATAERR : Sysexits::EX_OK
end

# Outputs a report of the linter run using the specified reporter.
def print_report(report, options)
reporter = options.fetch(:reporter, Reporter::DefaultReporter).new(log, report)
reporter.report_lints
reporter = options.fetch(:reporter,
HamlLint::Reporter::DefaultReporter).new(log)
reporter.display_report(report)
end

# Outputs a list of all currently available linters.
def print_available_linters
log.info 'Available linters:'

linter_names = LinterRegistry.linters.map do |linter|
linter_names = HamlLint::LinterRegistry.linters.map do |linter|
linter.name.split('::').last
end

Expand All @@ -92,10 +104,11 @@ def print_available_linters
end
end

# Outputs a list of currently available reporters.
def print_available_reporters
log.info 'Available reporters:'

reporter_names = Reporter.descendants.map do |reporter|
reporter_names = HamlLint::Reporter.descendants.map do |reporter|
reporter.name.split('::').last.sub(/Reporter$/, '').downcase
end

Expand All @@ -104,14 +117,18 @@ def print_available_reporters
end
end

# Outputs help documentation.
def print_help(options)
log.log options[:help]
end

# Outputs the application name and version.
def print_version
log.log "#{APP_NAME} #{HamlLint::VERSION}"
log.log "#{HamlLint::APP_NAME} #{HamlLint::VERSION}"
end

# Outputs the backtrace of an exception with instructions on how to report
# the issue.
def print_unexpected_exception(ex)
log.bold_error ex.message
log.error ex.backtrace.join("\n")
Expand Down
79 changes: 45 additions & 34 deletions lib/haml_lint/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
module HamlLint
# Stores configuration for haml-lint.
# Stores runtime configuration for the application.
#
# The purpose of this class is to validate and ensure all configurations
# satisfy some basic pre-conditions so other parts of the application don't
# have to check the configuration for errors. It should have no knowledge of
# how these configuration values are ultimately used.
class Configuration
# Internal hash storing the configuration.
attr_reader :hash

# Creates a configuration from the given options hash.
Expand All @@ -11,6 +17,14 @@ def initialize(options)
validate
end

# Access the configuration as if it were a hash.
#
# @param key [String]
# @return [Array,Hash,Number,String]
def [](key)
@hash[key]
end

# Compares this configuration with another.
#
# @param other [HamlLint::Configuration]
Expand All @@ -30,19 +44,9 @@ def for_linter(linter)
linter.name.split('::').last
when HamlLint::Linter
linter.name
else
linter.to_s
end

smart_merge(@hash['linters']['ALL'],
@hash['linters'].fetch(linter_name, {})).freeze
end

# Returns whether the specified linter is enabled by this configuration.
#
# @param linter [HamlLint::Linter,String]
def linter_enabled?(linter)
for_linter(linter)['enabled'] != false
@hash['linters'].fetch(linter_name, {}).dup.freeze
end

# Merges the given configuration with this one, returning a new
Expand All @@ -56,18 +60,14 @@ def merge(config)

private

# Validates the configuration for any invalid options, normalizing it where
# possible.
def validate
@hash = convert_nils_to_empty_hashes(@hash)
ensure_linter_section_exists(@hash)
end

# Merge two hashes such that nested hashes are merged rather than replaced.
#
# @param parent [Hash]
# @param child [Hash]
# @return [Hash]
def smart_merge(parent, child)
parent.merge(child) do |_key, old, new|
case old
when Array
old + Array(new)
when Hash
smart_merge(old, new)
else
Expand All @@ -76,21 +76,32 @@ def smart_merge(parent, child)
end
end

def ensure_linter_section_exists(hash)
hash['linters'] ||= {}
hash['linters']['ALL'] ||= {}
# Validates the configuration for any invalid options, normalizing it where
# possible.
def validate
ensure_exclude_option_array_exists
ensure_linter_section_exists
ensure_linter_include_exclude_arrays_exist
end

# Ensures the `exclude` global option is an array.
def ensure_exclude_option_array_exists
@hash['exclude'] = Array(@hash['exclude'])
end

def convert_nils_to_empty_hashes(hash)
hash.each_with_object({}) do |(key, value), h|
h[key] =
case value
when nil then {}
when Hash then convert_nils_to_empty_hashes(value)
else
value
end
h
# Ensures the `linters` configuration section exists.
def ensure_linter_section_exists
@hash['linters'] ||= {}
end

# Ensure `include` and `exclude` options for linters are arrays
# (since users can specify a single string glob pattern for convenience)
def ensure_linter_include_exclude_arrays_exist
@hash['linters'].keys.each do |linter_name|
%w[include exclude].each do |option|
linter_config = @hash['linters'][linter_name]
linter_config[option] = Array(linter_config[option])
end
end
end
end
Expand Down
28 changes: 22 additions & 6 deletions lib/haml_lint/configuration_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
module HamlLint
# Manages configuration file loading.
class ConfigurationLoader
DEFAULT_CONFIG_PATH = File.join(HAML_LINT_HOME, 'config', 'default.yml')
DEFAULT_CONFIG_PATH = File.join(HamlLint::HOME, 'config', 'default.yml')
CONFIG_FILE_NAME = '.haml-lint.yml'

class << self
# Load configuration file given the current working directory the
# application is running within.
def load_applicable_config
directory = File.expand_path(Dir.pwd)
config_file = possible_config_files(directory).find(&:file?)
Expand All @@ -19,33 +21,42 @@ def load_applicable_config
end
end

# Loads the built-in default configuration.
def default_configuration
@default_config ||= load_from_file(DEFAULT_CONFIG_PATH)
end

# Loads a configuration, ensuring it extends the default configuration.
#
# @param file [String]
# @return [HamlLint::Configuration]
def load_file(file)
config = load_from_file(file)

default_configuration.merge(config)
rescue => error
rescue Psych::SyntaxError, Errno::ENOENT => error
raise HamlLint::Exceptions::ConfigurationError,
"Unable to load configuration from '#{file}': #{error}",
error.backtrace
end

# Creates a configuration from the specified hash, ensuring it extends the
# default configuration.
#
# @param hash [Hash]
# @return [HamlLint::Configuration]
def load_hash(hash)
config = HamlLint::Configuration.new(hash)

default_configuration.merge(config)
rescue => error
raise HamlLint::Exceptions::ConfigurationError,
"Unable to load configuration from '#{file}': #{error}",
error.backtrace
end

private

# Parses and loads a configuration from the given file.
#
# @param file [String]
# @return [HamlLint::Configuration]
def load_from_file(file)
hash =
if yaml = YAML.load_file(file)
Expand All @@ -57,6 +68,11 @@ def load_from_file(file)
HamlLint::Configuration.new(hash)
end

# Returns a list of possible configuration files given the context of the
# specified directory.
#
# @param directory [String]
# @return [Array<Pathname>]
def possible_config_files(directory)
files = Pathname.new(directory)
.enum_for(:ascend)
Expand Down
2 changes: 1 addition & 1 deletion lib/haml_lint/constants.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Global application constants.
module HamlLint
HAML_LINT_HOME = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
HOME = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
APP_NAME = 'haml-lint'

REPO_URL = 'https://github.com/brigade/haml-lint'
Expand Down
Loading

0 comments on commit f222bfa

Please sign in to comment.