slim_lint 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/bin/slim-lint +0 -1
  3. data/lib/slim_lint/atom.rb +91 -0
  4. data/lib/slim_lint/capture_map.rb +17 -0
  5. data/lib/slim_lint/cli.rb +25 -8
  6. data/lib/slim_lint/configuration.rb +38 -31
  7. data/lib/slim_lint/configuration_loader.rb +21 -5
  8. data/lib/slim_lint/document.rb +20 -4
  9. data/lib/slim_lint/engine.rb +6 -0
  10. data/lib/slim_lint/file_finder.rb +13 -4
  11. data/lib/slim_lint/filters/inject_line_numbers.rb +7 -2
  12. data/lib/slim_lint/filters/sexp_converter.rb +4 -0
  13. data/lib/slim_lint/lint.rb +17 -1
  14. data/lib/slim_lint/linter/comment_control_statement.rb +2 -2
  15. data/lib/slim_lint/linter/consecutive_control_statements.rb +1 -16
  16. data/lib/slim_lint/linter/empty_control_statement.rb +1 -1
  17. data/lib/slim_lint/linter/redundant_div.rb +8 -5
  18. data/lib/slim_lint/linter/rubocop.rb +40 -18
  19. data/lib/slim_lint/linter/tag_case.rb +1 -1
  20. data/lib/slim_lint/linter.rb +19 -6
  21. data/lib/slim_lint/linter_registry.rb +13 -2
  22. data/lib/slim_lint/linter_selector.rb +74 -0
  23. data/lib/slim_lint/logger.rb +5 -9
  24. data/lib/slim_lint/matcher/anything.rb +9 -0
  25. data/lib/slim_lint/matcher/base.rb +19 -0
  26. data/lib/slim_lint/matcher/capture.rb +30 -0
  27. data/lib/slim_lint/matcher/nothing.rb +11 -0
  28. data/lib/slim_lint/options.rb +16 -6
  29. data/lib/slim_lint/rake_task.rb +15 -0
  30. data/lib/slim_lint/report.rb +7 -0
  31. data/lib/slim_lint/reporter/default_reporter.rb +2 -2
  32. data/lib/slim_lint/reporter/json_reporter.rb +15 -10
  33. data/lib/slim_lint/reporter.rb +17 -11
  34. data/lib/slim_lint/ruby_extractor.rb +2 -0
  35. data/lib/slim_lint/runner.rb +43 -39
  36. data/lib/slim_lint/sexp.rb +43 -30
  37. data/lib/slim_lint/sexp_visitor.rb +66 -28
  38. data/lib/slim_lint/utils.rb +17 -0
  39. data/lib/slim_lint/version.rb +1 -1
  40. data/lib/slim_lint.rb +10 -1
  41. metadata +10 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 90417095e752ffc02b70c6cf2c7c578642a7aced
4
- data.tar.gz: db37d397d726e455c2d98e565c1f0a4ba876c28f
3
+ metadata.gz: 66e3790d144eb2141cb93b53835a447cfe9e1db8
4
+ data.tar.gz: 7b07131e272e1b913926061eb37703d7e0738f01
5
5
  SHA512:
6
- metadata.gz: d08877671cd10ae219e055f65f16c9dc26800b4aac6fe0be74dcd669da2da0bcdf45ec7885b24c87333c7731fe1178a7c0c45259146ff7c285da6371b7da53da
7
- data.tar.gz: 1afec4e606480b536283d722121dc662fc0d6b2da4cd5bfeb3c93e4853b113404459a6e012546cd0fb179f99888983fa7ce0880f5f02bfcb5944acb163817c19
6
+ metadata.gz: d776fde028a429d5a44ae2cdc4092705e759a96f82dca6c2b59ad6a3fc1280823e86ecee38fa642f3720d048567ab31a3dd1bd5bf72a48ef3737c2b1d300843f
7
+ data.tar.gz: e617bc89440a83feefec3beabaa186ef57fb8c5433fd7b134610deaf631a4b4ad3c0e1e70474255145d2b40fc5d19ee3f19191ab102329b4f5a5dc27d9995717
data/bin/slim-lint CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'slim_lint'
4
3
  require 'slim_lint/cli'
5
4
 
6
5
  logger = SlimLint::Logger.new(STDOUT)
@@ -0,0 +1,91 @@
1
+ module SlimLint
2
+ # Represents an atomic, childless, literal value within an S-expression.
3
+ #
4
+ # This creates a light wrapper around literal values of S-expressions so we
5
+ # can make an {Atom} quack like a {Sexp} without being an {Sexp}.
6
+ class Atom
7
+ # Creates an atom from the specified value.
8
+ #
9
+ # @param value [Object]
10
+ def initialize(value)
11
+ @value = value
12
+ end
13
+
14
+ # Returns whether this atom is equivalent to another object.
15
+ #
16
+ # This defines a helper which unwraps the inner value of the atom to compare
17
+ # against a literal value, saving us having to do it ourselves everywhere
18
+ # else.
19
+ #
20
+ # @param other [Object]
21
+ # @return [Boolean]
22
+ def ==(other)
23
+ case other
24
+ when Atom
25
+ @value == other.instance_variable_get(:@value)
26
+ else
27
+ @value == other
28
+ end
29
+ end
30
+
31
+ # Returns whether this atom matches the given Sexp pattern.
32
+ #
33
+ # This exists solely to make an {Atom} quack like a {Sexp}, so we don't have
34
+ # to manually check the type when doing comparisons elsewhere.
35
+ #
36
+ # @param [Array, Object]
37
+ # @return [Boolean]
38
+ def match?(pattern)
39
+ # Delegate matching logic if we're comparing against a matcher
40
+ if pattern.is_a?(SlimLint::Matcher::Base)
41
+ return pattern.match?(@value)
42
+ end
43
+
44
+ @value == pattern
45
+ end
46
+
47
+ # Displays the string representation the value this {Atom} wraps.
48
+ #
49
+ # @return [String]
50
+ def to_s
51
+ @value.to_s
52
+ end
53
+
54
+ # Displays a string representation of this {Atom} suitable for debugging.
55
+ #
56
+ # @return [String]
57
+ def inspect
58
+ "<#Atom #{@value.inspect}>"
59
+ end
60
+
61
+ # Redirect methods to the value this {Atom} wraps.
62
+ #
63
+ # Again, this is for convenience so we don't need to manually unwrap the
64
+ # value ourselves. It's pretty magical, but results in much DRYer code.
65
+ #
66
+ # @param method_sym [Symbol] method that was called
67
+ # @param args [Array]
68
+ # @yield block that was passed to the method
69
+ def method_missing(method_sym, *args, &block)
70
+ if @value.respond_to?(method_sym)
71
+ @value.send(method_sym, *args, &block)
72
+ else
73
+ super
74
+ end
75
+ end
76
+
77
+ # Return whether this {Atom} or the value it wraps responds to the given
78
+ # message.
79
+ #
80
+ # @param method_sym [Symbol]
81
+ # @param include_private [Boolean]
82
+ # @return [Boolean]
83
+ def respond_to?(method_sym, include_private = false)
84
+ if super
85
+ true
86
+ else
87
+ @value.respond_to?(method_sym, include_private)
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,17 @@
1
+ module SlimLint
2
+ # Holds the list of captures, providing a convenient interface for accessing
3
+ # the values and unwrapping them on your behalf.
4
+ class CaptureMap < Hash
5
+ # Returns the captured value with the specified name.
6
+ #
7
+ # @param capture_name [Symbol]
8
+ # @return [Object]
9
+ def [](capture_name)
10
+ if key?(capture_name)
11
+ super.value
12
+ else
13
+ raise ArgumentError, "Capture #{capture_name.inspect} does not exist!"
14
+ end
15
+ end
16
+ end
17
+ end
data/lib/slim_lint/cli.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'slim_lint'
1
2
  require 'slim_lint/options'
2
3
 
3
4
  require 'sysexits'
@@ -5,8 +6,8 @@ require 'sysexits'
5
6
  module SlimLint
6
7
  # Command line application interface.
7
8
  class CLI
8
- attr_accessor :options
9
-
9
+ # Create a CLI that outputs to the specified logger.
10
+ #
10
11
  # @param logger [SlimLint::Logger]
11
12
  def initialize(logger)
12
13
  @log = logger
@@ -16,7 +17,7 @@ module SlimLint
16
17
  # based on those arguments.
17
18
  #
18
19
  # @param args [Array<String>] command line arguments
19
- # @return [Fixnum] exit status returned by the application
20
+ # @return [Integer] exit status code
20
21
  def run(args)
21
22
  options = SlimLint::Options.new.parse(args)
22
23
  act_on_options(options)
@@ -28,6 +29,9 @@ module SlimLint
28
29
 
29
30
  attr_reader :log
30
31
 
32
+ # Given the provided options, execute the appropriate command.
33
+ #
34
+ # @return [Integer] exit status code
31
35
  def act_on_options(options)
32
36
  log.color_enabled = options.fetch(:color, log.tty?)
33
37
 
@@ -48,6 +52,8 @@ module SlimLint
48
52
  end
49
53
  end
50
54
 
55
+ # Outputs a message and returns an appropriate error code for the specified
56
+ # exception.
51
57
  def handle_exception(ex)
52
58
  case ex
53
59
  when SlimLint::Exceptions::ConfigurationError
@@ -69,21 +75,27 @@ module SlimLint
69
75
  end
70
76
  end
71
77
 
78
+ # Scans the files specified by the given options for lints.
79
+ #
80
+ # @return [Integer] exit status code
72
81
  def scan_for_lints(options)
73
82
  report = Runner.new.run(options)
74
83
  print_report(report, options)
75
84
  report.failed? ? Sysexits::EX_DATAERR : Sysexits::EX_OK
76
85
  end
77
86
 
87
+ # Outputs a report of the linter run using the specified reporter.
78
88
  def print_report(report, options)
79
- reporter = options.fetch(:reporter, Reporter::DefaultReporter).new(log, report)
80
- reporter.report_lints
89
+ reporter = options.fetch(:reporter,
90
+ SlimLint::Reporter::DefaultReporter).new(log)
91
+ reporter.display_report(report)
81
92
  end
82
93
 
94
+ # Outputs a list of all currently available linters.
83
95
  def print_available_linters
84
96
  log.info 'Available linters:'
85
97
 
86
- linter_names = LinterRegistry.linters.map do |linter|
98
+ linter_names = SlimLint::LinterRegistry.linters.map do |linter|
87
99
  linter.name.split('::').last
88
100
  end
89
101
 
@@ -92,10 +104,11 @@ module SlimLint
92
104
  end
93
105
  end
94
106
 
107
+ # Outputs a list of currently available reporters.
95
108
  def print_available_reporters
96
109
  log.info 'Available reporters:'
97
110
 
98
- reporter_names = Reporter.descendants.map do |reporter|
111
+ reporter_names = SlimLint::Reporter.descendants.map do |reporter|
99
112
  reporter.name.split('::').last.sub(/Reporter$/, '').downcase
100
113
  end
101
114
 
@@ -104,14 +117,18 @@ module SlimLint
104
117
  end
105
118
  end
106
119
 
120
+ # Outputs help documentation.
107
121
  def print_help(options)
108
122
  log.log options[:help]
109
123
  end
110
124
 
125
+ # Outputs the application name and version.
111
126
  def print_version
112
- log.log "#{APP_NAME} #{SlimLint::VERSION}"
127
+ log.log "#{SlimLint::APP_NAME} #{SlimLint::VERSION}"
113
128
  end
114
129
 
130
+ # Outputs the backtrace of an exception with instructions on how to report
131
+ # the issue.
115
132
  def print_unexpected_exception(ex)
116
133
  log.bold_error ex.message
117
134
  log.error ex.backtrace.join("\n")
@@ -1,6 +1,12 @@
1
1
  module SlimLint
2
2
  # Stores runtime configuration for the application.
3
+ #
4
+ # The purpose of this class is to validate and ensure all configurations
5
+ # satisfy some basic pre-conditions so other parts of the application don't
6
+ # have to check the configuration for errors. It should have no knowledge of
7
+ # how these configuration values are ultimately used.
3
8
  class Configuration
9
+ # Internal hash storing the configuration.
4
10
  attr_reader :hash
5
11
 
6
12
  # Creates a configuration from the given options hash.
@@ -11,6 +17,8 @@ module SlimLint
11
17
  validate
12
18
  end
13
19
 
20
+ # Access the configuration as if it were a hash.
21
+ #
14
22
  # @param key [String]
15
23
  # @return [Array,Hash,Number,String]
16
24
  def [](key)
@@ -36,19 +44,9 @@ module SlimLint
36
44
  linter.name.split('::').last
37
45
  when SlimLint::Linter
38
46
  linter.name
39
- else
40
- linter.to_s
41
47
  end
42
48
 
43
- smart_merge(@hash['linters']['ALL'],
44
- @hash['linters'].fetch(linter_name, {})).freeze
45
- end
46
-
47
- # Returns whether the specified linter is enabled by this configuration.
48
- #
49
- # @param linter [SlimLint::Linter,String]
50
- def linter_enabled?(linter)
51
- for_linter(linter)['enabled'] != false
49
+ @hash['linters'].fetch(linter_name, {}).dup.freeze
52
50
  end
53
51
 
54
52
  # Merges the given configuration with this one, returning a new
@@ -62,13 +60,11 @@ module SlimLint
62
60
 
63
61
  private
64
62
 
65
- # Validates the configuration for any invalid options, normalizing it where
66
- # possible.
67
- def validate
68
- @hash = convert_nils_to_empty_hashes(@hash)
69
- ensure_linter_section_exists(@hash)
70
- end
71
-
63
+ # Merge two hashes such that nested hashes are merged rather than replaced.
64
+ #
65
+ # @param parent [Hash]
66
+ # @param child [Hash]
67
+ # @return [Hash]
72
68
  def smart_merge(parent, child)
73
69
  parent.merge(child) do |_key, old, new|
74
70
  case old
@@ -80,21 +76,32 @@ module SlimLint
80
76
  end
81
77
  end
82
78
 
83
- def ensure_linter_section_exists(hash)
84
- hash['linters'] ||= {}
85
- hash['linters']['ALL'] ||= {}
79
+ # Validates the configuration for any invalid options, normalizing it where
80
+ # possible.
81
+ def validate
82
+ ensure_exclude_option_array_exists
83
+ ensure_linter_section_exists
84
+ ensure_linter_include_exclude_arrays_exist
86
85
  end
87
86
 
88
- def convert_nils_to_empty_hashes(hash)
89
- hash.each_with_object({}) do |(key, value), h|
90
- h[key] =
91
- case value
92
- when nil then {}
93
- when Hash then convert_nils_to_empty_hashes(value)
94
- else
95
- value
96
- end
97
- h
87
+ # Ensures the `exclude` global option is an array.
88
+ def ensure_exclude_option_array_exists
89
+ @hash['exclude'] = Array(@hash['exclude'])
90
+ end
91
+
92
+ # Ensures the `linters` configuration section exists.
93
+ def ensure_linter_section_exists
94
+ @hash['linters'] ||= {}
95
+ end
96
+
97
+ # Ensure `include` and `exclude` options for linters are arrays
98
+ # (since users can specify a single string glob pattern for convenience)
99
+ def ensure_linter_include_exclude_arrays_exist
100
+ @hash['linters'].keys.each do |linter_name|
101
+ %w[include exclude].each do |option|
102
+ linter_config = @hash['linters'][linter_name]
103
+ linter_config[option] = Array(linter_config[option])
104
+ end
98
105
  end
99
106
  end
100
107
  end
@@ -8,6 +8,8 @@ module SlimLint
8
8
  CONFIG_FILE_NAME = '.slim-lint.yml'
9
9
 
10
10
  class << self
11
+ # Load configuration file given the current working directory the
12
+ # application is running within.
11
13
  def load_applicable_config
12
14
  directory = File.expand_path(Dir.pwd)
13
15
  config_file = possible_config_files(directory).find(&:file?)
@@ -19,33 +21,42 @@ module SlimLint
19
21
  end
20
22
  end
21
23
 
24
+ # Loads the built-in default configuration.
22
25
  def default_configuration
23
26
  @default_config ||= load_from_file(DEFAULT_CONFIG_PATH)
24
27
  end
25
28
 
26
29
  # Loads a configuration, ensuring it extends the default configuration.
30
+ #
31
+ # @param file [String]
32
+ # @return [SlimLint::Configuration]
27
33
  def load_file(file)
28
34
  config = load_from_file(file)
29
35
 
30
36
  default_configuration.merge(config)
31
- rescue => error
37
+ rescue Psych::SyntaxError, Errno::ENOENT => error
32
38
  raise SlimLint::Exceptions::ConfigurationError,
33
39
  "Unable to load configuration from '#{file}': #{error}",
34
40
  error.backtrace
35
41
  end
36
42
 
43
+ # Creates a configuration from the specified hash, ensuring it extends the
44
+ # default configuration.
45
+ #
46
+ # @param hash [Hash]
47
+ # @return [SlimLint::Configuration]
37
48
  def load_hash(hash)
38
49
  config = SlimLint::Configuration.new(hash)
39
50
 
40
51
  default_configuration.merge(config)
41
- rescue => error
42
- raise SlimLint::Exceptions::ConfigurationError,
43
- "Unable to load configuration from '#{file}': #{error}",
44
- error.backtrace
45
52
  end
46
53
 
47
54
  private
48
55
 
56
+ # Parses and loads a configuration from the given file.
57
+ #
58
+ # @param file [String]
59
+ # @return [SlimLint::Configuration]
49
60
  def load_from_file(file)
50
61
  hash =
51
62
  if yaml = YAML.load_file(file)
@@ -57,6 +68,11 @@ module SlimLint
57
68
  SlimLint::Configuration.new(hash)
58
69
  end
59
70
 
71
+ # Returns a list of possible configuration files given the context of the
72
+ # specified directory.
73
+ #
74
+ # @param directory [String]
75
+ # @return [Array<Pathname>]
60
76
  def possible_config_files(directory)
61
77
  files = Pathname.new(directory)
62
78
  .enum_for(:ascend)
@@ -1,7 +1,23 @@
1
1
  module SlimLint
2
2
  # Represents a parsed Slim document and its associated metadata.
3
3
  class Document
4
- attr_reader :config, :file, :sexp, :source, :source_lines
4
+ # File name given to source code parsed from just a string.
5
+ STRING_SOURCE = '(string)'
6
+
7
+ # @return [SlimLint::Configuration] Configuration used to parse template
8
+ attr_reader :config
9
+
10
+ # @return [String] Slim template file path
11
+ attr_reader :file
12
+
13
+ # @return [SlimLint::Sexp] Sexpression representing the parsed document
14
+ attr_reader :sexp
15
+
16
+ # @return [String] original source code
17
+ attr_reader :source
18
+
19
+ # @return [Array<String>] original source code as an array of lines
20
+ attr_reader :source_lines
5
21
 
6
22
  # Parses the specified Slim code into a {Document}.
7
23
  #
@@ -11,7 +27,7 @@ module SlimLint
11
27
  # @raise [Slim::Parser::Error] if there was a problem parsing the document
12
28
  def initialize(source, options)
13
29
  @config = options[:config]
14
- @file = options.fetch(:file, '(string)')
30
+ @file = options.fetch(:file, STRING_SOURCE)
15
31
 
16
32
  process_source(source)
17
33
  end
@@ -24,8 +40,8 @@ module SlimLint
24
40
  @source = strip_frontmatter(source)
25
41
  @source_lines = @source.split("\n")
26
42
 
27
- @engine = SlimLint::Engine.new(file: @file)
28
- @sexp = @engine.call(source)
43
+ engine = SlimLint::Engine.new(file: @file)
44
+ @sexp = engine.parse(source)
29
45
  end
30
46
 
31
47
  # Removes YAML frontmatter
@@ -23,5 +23,11 @@ module SlimLint
23
23
 
24
24
  # Annotates Sexps with line numbers
25
25
  use SlimLint::Filters::InjectLineNumbers
26
+
27
+ # Parses the given source code into a Sexp.
28
+ #
29
+ # @param source [String] source code to parse
30
+ # @return [SlimLint::Sexp] parsed Sexp
31
+ alias_method :parse, :call
26
32
  end
27
33
  end
@@ -8,6 +8,8 @@ module SlimLint
8
8
  # is specified instead of a file.
9
9
  VALID_EXTENSIONS = %w[.slim]
10
10
 
11
+ # Create a file finder using the specified configuration.
12
+ #
11
13
  # @param config [SlimLint::Configuration]
12
14
  def initialize(config)
13
15
  @config = config
@@ -22,15 +24,18 @@ module SlimLint
22
24
  def find(patterns, excluded_patterns)
23
25
  extract_files_from(patterns).reject do |file|
24
26
  excluded_patterns.any? do |exclusion_glob|
25
- ::File.fnmatch?(exclusion_glob, file,
26
- ::File::FNM_PATHNAME | # Wildcards don't match path separators
27
- ::File::FNM_DOTMATCH) # `*` wildcard matches dotfiles
27
+ SlimLint::Utils.any_glob_matches?(exclusion_glob, file)
28
28
  end
29
29
  end
30
30
  end
31
31
 
32
32
  private
33
33
 
34
+ # Extract the list of matching files given the list of glob patterns, file
35
+ # paths, and directories.
36
+ #
37
+ # @param patterns [Array<String>]
38
+ # @return [Array<String>]
34
39
  def extract_files_from(patterns) # rubocop:disable MethodLength
35
40
  files = []
36
41
 
@@ -57,9 +62,13 @@ module SlimLint
57
62
  end
58
63
  end
59
64
 
60
- files.uniq
65
+ files.uniq.sort
61
66
  end
62
67
 
68
+ # Whether the given file should be treated as a Slim file.
69
+ #
70
+ # @param file [String]
71
+ # @return [Boolean]
63
72
  def slim_file?(file)
64
73
  return false unless ::FileTest.file?(file)
65
74
 
@@ -5,8 +5,13 @@ module SlimLint::Filters
5
5
  # This is a hack that allows us to access line information directly from the
6
6
  # S-expressions, which makes a lot of other tasks easier.
7
7
  class InjectLineNumbers < Temple::Filter
8
- NEWLINE_SEXP = [:newline]
8
+ # {Sexp} representing a newline.
9
+ NEWLINE_SEXP = SlimLint::Sexp.new([:newline])
9
10
 
11
+ # Annotates the given {SlimLint::Sexp} with line number information.
12
+ #
13
+ # @param sexp [SlimLint::Sexp]
14
+ # @return [SlimLint::Sexp]
10
15
  def call(sexp)
11
16
  @line_number = 1
12
17
  traverse(sexp)
@@ -16,7 +21,7 @@ module SlimLint::Filters
16
21
  private
17
22
 
18
23
  # Traverses an {Sexp}, annotating it with line numbers by searching for
19
- # :newline abstractions within it.
24
+ # newline abstractions within it.
20
25
  #
21
26
  # @param sexp [SlimLint::Sexp]
22
27
  def traverse(sexp)
@@ -4,6 +4,10 @@ module SlimLint::Filters
4
4
  # These {SlimLint::Sexp}s include additional helpers that makes working with
5
5
  # them more pleasant.
6
6
  class SexpConverter < Temple::Filter
7
+ # Converts the given {Array} to a {SlimLint::Sexp}.
8
+ #
9
+ # @param array_sexp [Array]
10
+ # @return [SlimLint::Sexp]
7
11
  def call(array_sexp)
8
12
  SlimLint::Sexp.new(array_sexp)
9
13
  end
@@ -1,7 +1,20 @@
1
1
  module SlimLint
2
2
  # Contains information about a problem or issue with a Slim document.
3
3
  class Lint
4
- attr_reader :filename, :line, :linter, :message, :severity
4
+ # @return [String] file path to which the lint applies
5
+ attr_reader :filename
6
+
7
+ # @return [String] line number of the file the lint corresponds to
8
+ attr_reader :line
9
+
10
+ # @return [SlimLint::Linter] linter that reported the lint
11
+ attr_reader :linter
12
+
13
+ # @return [String] error/warning message to display to user
14
+ attr_reader :message
15
+
16
+ # @return [Symbol] whether this lint is a warning or an error
17
+ attr_reader :severity
5
18
 
6
19
  # Creates a new lint.
7
20
  #
@@ -18,6 +31,9 @@ module SlimLint
18
31
  @severity = severity
19
32
  end
20
33
 
34
+ # Return whether this lint has a severity of error.
35
+ #
36
+ # @return [Boolean]
21
37
  def error?
22
38
  @severity == :error
23
39
  end
@@ -5,13 +5,13 @@ module SlimLint
5
5
 
6
6
  on [:slim, :control] do |sexp|
7
7
  _, _, code = sexp
8
- next unless code =~ /\A\s*#/
8
+ next unless code[/\A\s*#/]
9
9
 
10
10
  comment = code[/\A\s*#(.*\z)/, 1]
11
11
 
12
12
  report_lint(sexp,
13
13
  "Slim code comments (`/#{comment}`) are preferred over " \
14
- "control statement comments (`-# #{comment}`)")
14
+ "control statement comments (`-##{comment}`)")
15
15
  end
16
16
  end
17
17
  end
@@ -6,27 +6,12 @@ module SlimLint
6
6
 
7
7
  on [:multi] do |sexp|
8
8
  Utils.for_consecutive_items(sexp,
9
- method(:code_sexp?),
9
+ ->(nested_sexp) { nested_sexp.match?([:slim, :control]) },
10
10
  config['max_consecutive'] + 1) do |group|
11
11
  report_lint(group.first,
12
12
  "#{group.count} consecutive control statements can be " \
13
13
  'merged into a single `ruby:` filter')
14
14
  end
15
15
  end
16
-
17
- private
18
-
19
- # Returns whether the given Sexp is a :code abstraction.
20
- #
21
- # @param sexp [SlimLint::Sexp]
22
- # @return [Boolean]
23
- def code_sexp?(sexp)
24
- # TODO: Switch this with a built-in method on the {Sexp} object itself
25
- sexp.is_a?(Sexp) && sexp.match?([:slim, :control])
26
- end
27
-
28
- def newline_sexp?(sexp)
29
- sexp.is_a?(Sexp) && sexp.first == :newline
30
- end
31
16
  end
32
17
  end
@@ -5,7 +5,7 @@ module SlimLint
5
5
 
6
6
  on [:slim, :control] do |sexp|
7
7
  _, _, code = sexp
8
- next unless code =~ /\A\s*\Z/
8
+ next unless code[/\A\s*\Z/]
9
9
 
10
10
  report_lint(sexp, 'Empty control statement can be removed')
11
11
  end
@@ -6,12 +6,15 @@ module SlimLint
6
6
 
7
7
  MESSAGE = '`div` is redundant when %s attribute shortcut is present'
8
8
 
9
- on [:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'class', [:static]]]] do |sexp|
10
- report_lint(sexp, MESSAGE % 'class')
11
- end
9
+ on [:html, :tag, 'div',
10
+ [:html, :attrs,
11
+ [:html, :attr,
12
+ capture(:attr_name, anything),
13
+ [:static]]]] do |sexp|
14
+ attr = captures[:attr_name]
15
+ next unless %w[class id].include?(attr)
12
16
 
13
- on [:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'id', [:static]]]] do |sexp|
14
- report_lint(sexp, MESSAGE % 'id')
17
+ report_lint(sexp, MESSAGE % attr)
15
18
  end
16
19
  end
17
20
  end