Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,8 @@ linters:
```

Every rule accepts `include:` and `exclude:`, lists of glob patterns matched against paths relative to
`.super-slim.yml` (`**/` matches any number of directories).
`.super-slim.yml` (`**/` matches any number of directories). A rule, an option or a setting the file misspells
stops `lint` and `format` with an error that names it, rather than being ignored.

A comment silences rules on the next tag or code line, blank lines and comments skipped, and on the lines it goes on
over. The linter reports a rule name it doesn't know:
Expand Down
18 changes: 17 additions & 1 deletion lib/super_slim/linter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ def each_tag(&) = tag_visits.each(&)
# The layout rules: what `super-slim format` rewrites.
LAYOUT_RULE_NAMES = %w[Indentation Tab TrailingWhitespace EmptyLines ControlStatementSpacing RedundantDiv].freeze
GLOB_OPTIONS = %w[include exclude].freeze
SETTINGS = %w[linters format fmt tailwind_classes].freeze
FORMAT_SETTINGS = %w[max_line_length attribute_delimiters].freeze
# What the default of a rule's option asks of the value that replaces it.
VALUE_KIND_BY_DEFAULT_CLASS = {
TrueClass => ["true or false", [TrueClass, FalseClass]],
Expand All @@ -102,6 +104,7 @@ def self.configuration(directory = Dir.pwd)
def self.check_configuration(configuration)
raise Error, "#{CONFIG_FILE}: expected a mapping of settings" unless configuration.is_a?(Hash)

check_names(configuration.keys, SETTINGS, "a setting")
%w[linters format fmt].each do |key|
raise Error, "#{CONFIG_FILE}: `#{key}` must be a mapping" unless configuration.fetch(key, {}).is_a?(Hash)
end
Expand All @@ -118,7 +121,11 @@ def self.check_rule_options(rule_name, options)
globs = Array(options[key])
raise Error, "#{CONFIG_FILE}: `#{rule_name}: #{key}` must be a list of globs" unless globs.all?(String)
end
rule_class = Rules.all.find { _1.rule_name == rule_name } or return
rule_class = Rules.all.find { _1.rule_name == rule_name }
raise Error, "#{CONFIG_FILE}: `#{rule_name}` is not a rule of super-slim lint" unless rule_class

preset_option = (PRESETS.any? { |_preset, options_by_rule| options_by_rule.key?(rule_name) }) ? ["preset"] : []
check_names(options.keys, rule_class::DEFAULTS.keys.map(&:to_s) + GLOB_OPTIONS + preset_option, "an option of #{rule_name}")
rule_class::DEFAULTS.each do |key, default|
next unless options.key?(key.to_s)

Expand All @@ -129,6 +136,7 @@ def self.check_rule_options(rule_name, options)
private_class_method :check_rule_options

def self.check_format_options(key, options)
check_names(options.keys, FORMAT_SETTINGS, "a setting of `#{key}`")
max_line_length = options["max_line_length"]
wraps_nothing_or_at_a_length = max_line_length.nil? || (max_line_length.is_a?(Integer) && max_line_length.positive?)
raise Error, "#{CONFIG_FILE}: `#{key}: max_line_length` must be a whole number above 0" unless wraps_nothing_or_at_a_length
Expand All @@ -140,6 +148,14 @@ def self.check_format_options(key, options)
end
private_class_method :check_format_options

# A misspelt name is a setting that does nothing and says nothing: the rule it meant to turn off stays on.
def self.check_names(names, known_names, kind)
unknown_name = (names - known_names).first or return

raise Error, "#{CONFIG_FILE}: `#{unknown_name}` is not #{kind}, use one of: #{known_names.join(", ")}"
end
private_class_method :check_names

# include: and exclude: globs match paths relative to `root`, the directory of .super-slim.yml.
def initialize(options_by_rule_name = {}, root = Dir.pwd)
@root = File.join(File.expand_path(root), "")
Expand Down
16 changes: 16 additions & 0 deletions test/cli_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,22 @@ def test_a_configuration_of_the_wrong_shape_fails_with_a_usage_error
end
end

def test_a_name_the_configuration_misspells_fails_with_a_usage_error
{
"linters:\n LineLenght:\n max: 150\n" => "`LineLenght` is not a rule of super-slim lint",
"linters:\n LineLength:\n maximum: 150\n" => "`maximum` is not an option of LineLength, use one of: enabled, max, include, exclude",
"linters:\n LineLength:\n preset: bootstrap5\n" => "`preset` is not an option of LineLength, use one of: enabled, max, include, exclude",
"formatter:\n max_line_length: 120\n" => "`formatter` is not a setting, use one of: linters, format, fmt, tailwind_classes",
"format:\n line_length: 120\n" => "`line_length` is not a setting of `format`, use one of: max_line_length, attribute_delimiters"
}.each do |configuration, message|
in_templates("p Hello\n") do |directory|
File.write(File.join(directory, ".super-slim.yml"), configuration)

assert_output("", ".super-slim.yml: #{message}\n") { assert_equal SuperSlim::CLI::EX_USAGE, SuperSlim::CLI.new.call(["lint", "."]) }
end
end
end

def test_a_preset_table_and_a_line_length_left_empty_are_no_error
in_templates("p Hello\n") do |directory|
File.write(File.join(directory, ".super-slim.yml"), "linters:\n DeprecatedAttributes:\n preset: bootstrap5\n attributes:\nformat:\n max_line_length:\n")
Expand Down
Loading