haml_lint 0.74.0 → 0.76.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +2 -2
  3. data/config/forced_rubocop_config.yml +13 -13
  4. data/lib/haml_lint/document.rb +3 -3
  5. data/lib/haml_lint/exceptions.rb +2 -2
  6. data/lib/haml_lint/extensions/haml_util_unescape_interpolation_tracking.rb +2 -2
  7. data/lib/haml_lint/haml_visitor.rb +1 -1
  8. data/lib/haml_lint/lint.rb +8 -3
  9. data/lib/haml_lint/linter/alignment_tabs.rb +20 -3
  10. data/lib/haml_lint/linter/consecutive_silent_scripts.rb +68 -1
  11. data/lib/haml_lint/linter/html_attributes.rb +71 -2
  12. data/lib/haml_lint/linter/rubocop.rb +11 -9
  13. data/lib/haml_lint/linter/ruby_comments.rb +1 -1
  14. data/lib/haml_lint/linter/space_before_script.rb +1 -1
  15. data/lib/haml_lint/linter/unescaped_html.rb +1 -1
  16. data/lib/haml_lint/linter/unnecessary_string_output.rb +77 -13
  17. data/lib/haml_lint/linter.rb +7 -4
  18. data/lib/haml_lint/node_transformer.rb +2 -2
  19. data/lib/haml_lint/reporter/hash_reporter.rb +2 -0
  20. data/lib/haml_lint/reporter/utils.rb +2 -0
  21. data/lib/haml_lint/ruby_extraction/base_chunk.rb +3 -3
  22. data/lib/haml_lint/ruby_extraction/chunk_extractor.rb +11 -11
  23. data/lib/haml_lint/ruby_extraction/coordinator.rb +2 -2
  24. data/lib/haml_lint/ruby_extraction/implicit_end_chunk.rb +1 -1
  25. data/lib/haml_lint/ruby_extraction/non_ruby_filter_chunk.rb +1 -1
  26. data/lib/haml_lint/ruby_extraction/placeholder_marker_chunk.rb +1 -1
  27. data/lib/haml_lint/ruby_extraction/script_chunk.rb +1 -1
  28. data/lib/haml_lint/runner.rb +15 -1
  29. data/lib/haml_lint/spec/matchers/report_lint.rb +17 -6
  30. data/lib/haml_lint/tree/comment_node.rb +1 -1
  31. data/lib/haml_lint/tree/doctype_node.rb +1 -1
  32. data/lib/haml_lint/tree/filter_node.rb +1 -1
  33. data/lib/haml_lint/tree/haml_comment_node.rb +2 -2
  34. data/lib/haml_lint/tree/node.rb +3 -3
  35. data/lib/haml_lint/tree/root_node.rb +2 -2
  36. data/lib/haml_lint/tree/silent_script_node.rb +1 -1
  37. data/lib/haml_lint/tree/tag_node.rb +14 -2
  38. data/lib/haml_lint/utils.rb +1 -1
  39. data/lib/haml_lint/version.rb +1 -1
  40. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50729598b076118b141b2cf9552e8ada142c34ed52e3bd3e1a398948355ebfc2
4
- data.tar.gz: e03f59f848bfa5a5a2d792e4ee347aaf10c956b7d47920594e87e9f283f8cb1f
3
+ metadata.gz: 188328ae96f24b3a739665a5d3ccc7271a753afbe0ae63a1fbe3f33897492caa
4
+ data.tar.gz: 52b39c0992433545f610c7c18af3d06b224027edea3a9cdb97f71c7c1cf4f8c5
5
5
  SHA512:
6
- metadata.gz: 7ce36a007738cb26bd4034a16cd00861f64db28ce0eefcb9dcef1edca25ee09d61521eed7dfa9b3b5bb558f0c379dde10fcbfdae3ecb72a3b1b3a24877db5188
7
- data.tar.gz: 8601c85624f55a50dc33296ea88066eb935b7f5bc5843a2e2179204a7d2a259da9dcc95ee8543bc1b7205991f5d589e83f384d085e1884dfaa37ef8ed15fd9f2
6
+ metadata.gz: 31c53c1177acc7679c9f3c5b633a2b14278252f447ddea3b03c9d49e82d6e3d4587d44803e7b2b5ca677b726702c1a716cb02c848b8d2b71ed7bd402b78d8519
7
+ data.tar.gz: 253e971535ea2d8c38340e2e13a882fbe215cded72f5faa96b26dd88bb39024efe5b03d8334cc7692de4edbab2a7e49db9d4bb55a6a55e10319b52e3770a9ea4
data/config/default.yml CHANGED
@@ -3,7 +3,7 @@
3
3
  # This is an opinionated list of which hooks are valuable to run and what their
4
4
  # out of the box settings should be.
5
5
 
6
- # Whether to ignore frontmatter at the beginning of HAML documents for
6
+ # Whether to ignore frontmatter at the beginning of Haml documents for
7
7
  # frameworks such as Jekyll/Middleman
8
8
  skip_frontmatter: false
9
9
 
@@ -89,7 +89,7 @@ linters:
89
89
  RuboCop:
90
90
  enabled: true
91
91
  # Cops listed here are skipped entirely (passed to RuboCop via `--except`), both when
92
- # reporting and when auto-correcting. This is the way to disable a cop only for HAML files,
92
+ # reporting and when auto-correcting. This is the way to disable a cop only for Haml files,
93
93
  # and the only way to disable a forced cop (see config/forced_rubocop_config.yml), since the
94
94
  # forced configuration always overrides `Enabled: false` set in your own .rubocop.yml.
95
95
  ignored_cops: []
@@ -1,7 +1,7 @@
1
1
  # These are some configurations that are required for RuboCop because:
2
- # * HAML-Lint compiles ruby code with a particular format. If the rules mis-match that format,
3
- # HAML-Lint would generate lints that the user cannot fix.
4
- # * HAML-Lint can autocorrect code only if the result matches some specific format
2
+ # * Haml-Lint compiles ruby code with a particular format. If the rules mis-match that format,
3
+ # Haml-Lint would generate lints that the user cannot fix.
4
+ # * Haml-Lint can autocorrect code only if the result matches some specific format
5
5
  #
6
6
  # So these configuration should not be overwritable by users.
7
7
 
@@ -31,17 +31,17 @@ Layout/CaseIndentation:
31
31
  IndentOneStep: false # Need to force the `false`
32
32
 
33
33
  # Need this cop so that code gets formatted similarly to Haml's indentation,
34
- # since HAML-Lint relies on Ruby's indentation being the same as Haml's.
34
+ # since Haml-Lint relies on Ruby's indentation being the same as Haml's.
35
35
  Layout/ElseAlignment:
36
36
  Enabled: true
37
37
 
38
38
  # Need this cop so that code gets formatted similarly to Haml's indentation,
39
- # since HAML-Lint relies on Ruby's indentation being the same as Haml's.
39
+ # since Haml-Lint relies on Ruby's indentation being the same as Haml's.
40
40
  Layout/EndAlignment:
41
41
  EnforcedStyleAlignWith: start_of_line
42
42
  Enabled: true
43
43
 
44
- # We generate the ruby content, this is basically useless and should be a lint in HAML-Lint
44
+ # We generate the ruby content, this is basically useless and should be a lint in Haml-Lint
45
45
  Layout/EndOfLine:
46
46
  Enabled: false
47
47
 
@@ -90,14 +90,14 @@ Metrics/BlockNesting:
90
90
  Naming/FileName:
91
91
  Enabled: false
92
92
 
93
- # HAML doesn't properly support multiline blocks using { }, only using do/end.
93
+ # Haml doesn't properly support multiline blocks using { }, only using do/end.
94
94
  # If you don't consider the { } block for indentation, things "works", but the indentation is misleading.
95
95
  # For example, this works:
96
96
  # - a = lambda {
97
97
  # - if abc
98
98
  # - something
99
99
  # - }
100
- # But if you indented the 2 lines within { }, then HAML would add an extra `end` and the generated
100
+ # But if you indented the 2 lines within { }, then Haml would add an extra `end` and the generated
101
101
  # ruby would be invalid.
102
102
  Style/BlockDelimiters:
103
103
  # So we need this cop to cleanup those cases and turn them to `end`.
@@ -106,7 +106,7 @@ Style/BlockDelimiters:
106
106
  # We don't allow the default "Can be anything" exception for lambda/proc
107
107
  <%= rubocop_version < '1.33' ? 'IgnoredMethods' : 'AllowedMethods' %>: []
108
108
 
109
- # We don't support correcting HAML comments
109
+ # We don't support correcting Haml comments
110
110
  Style/CommentAnnotation:
111
111
  AutoCorrect: false
112
112
 
@@ -144,7 +144,7 @@ Style/RedundantStringEscape:
144
144
  <% end %>
145
145
 
146
146
  # In some case, this cop can cause a change in the spacing.
147
- # In HAML 5.2, going from (absurd example for clarity):
147
+ # In Haml 5.2, going from (absurd example for clarity):
148
148
  # = 'abc' rescue nil
149
149
  # = 'def'
150
150
  # to:
@@ -161,7 +161,7 @@ Style/RescueModifier:
161
161
 
162
162
  # Cops that remove commas can be a problem when lines are split on multiple ones.
163
163
  # If we have a big array on more than one line, the removal of the comma generates
164
- # invalid HAML
164
+ # invalid Haml
165
165
  Style/SymbolArray:
166
166
  Enabled: false
167
167
 
@@ -170,7 +170,7 @@ Style/UnlessElse:
170
170
  AutoCorrect: false
171
171
 
172
172
  # If an array of strings was on multiple lines, this cop will make a %w(...) on multiple lines.
173
- # Without the comma at the end of the first line, there the resulting HAML will be invalid, since the only
173
+ # Without the comma at the end of the first line, there the resulting Haml will be invalid, since the only
174
174
  # case where a script can change line is after a comma.
175
175
  Style/WordArray:
176
176
  AutoCorrect: false
@@ -181,7 +181,7 @@ Layout/TrailingEmptyLines:
181
181
 
182
182
  <% if rubocop_version < '1.8.1' %>
183
183
  # There were a few bugs with this cop that got fixed in this version.
184
- # Before, those bugs would generate invalid Ruby code and that would make it look like HAML-lint is
184
+ # Before, those bugs would generate invalid Ruby code and that would make it look like Haml-lint is
185
185
  # responsible, at least from the user's point of view.
186
186
  Style/StringConcatenation:
187
187
  Enabled: false
@@ -132,8 +132,8 @@ module HamlLint
132
132
  # @param original_tree [Haml::Parser::ParseNode]
133
133
  # @return [Haml::Tree::Node]
134
134
  def process_tree(original_tree)
135
- # Remove the trailing empty HAML comment that the parser creates to signal
136
- # the end of the HAML document
135
+ # Remove the trailing empty Haml comment that the parser creates to signal
136
+ # the end of the Haml document
137
137
  if Gem::Requirement.new('~> 4.0.0').satisfied_by?(Gem.loaded_specs['haml'].version)
138
138
  original_tree.children.pop
139
139
  end
@@ -142,7 +142,7 @@ module HamlLint
142
142
  convert_tree(original_tree)
143
143
  end
144
144
 
145
- # Converts a HAML parse tree to a tree of {HamlLint::Tree::Node} objects.
145
+ # Converts a Haml parse tree to a tree of {HamlLint::Tree::Node} objects.
146
146
  #
147
147
  # This provides a cleaner interface with which the linters can interact with
148
148
  # the parse tree.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Collection of exceptions that can be raised by the HAML Lint application.
3
+ # Collection of exceptions that can be raised by the Haml Lint application.
4
4
  module HamlLint::Exceptions
5
5
  # Raised when a {Configuration} could not be loaded from a file.
6
6
  class ConfigurationError < StandardError; end
@@ -17,7 +17,7 @@ module HamlLint::Exceptions
17
17
  # Raised when an invalid file path is specified
18
18
  class InvalidFilePath < StandardError; end
19
19
 
20
- # Raised when a problem occurs parsing a HAML document.
20
+ # Raised when a problem occurs parsing a Haml document.
21
21
  class ParseError < ::Haml::SyntaxError; end
22
22
 
23
23
  # Raised when attempting to execute `Runner` with options that would result in
@@ -14,8 +14,8 @@ module Haml::Util
14
14
  Thread.current[:haml_lint_unescape_interpolation_to_original_cache] ||= {}
15
15
  end
16
16
 
17
- # As soon as a HamlLint::Document has finished processing a HAML source, this gets called to
18
- # get a copy of this cache and clear up for the next HAML processing
17
+ # As soon as a HamlLint::Document has finished processing a Haml source, this gets called to
18
+ # get a copy of this cache and clear up for the next Haml processing
19
19
  def self.unescape_interpolation_to_original_cache_take_and_wipe
20
20
  value = unescape_interpolation_to_original_cache.dup
21
21
  unescape_interpolation_to_original_cache.clear
@@ -2,7 +2,7 @@
2
2
 
3
3
  module HamlLint
4
4
  # Provides an interface which when included allows a class to visit nodes in
5
- # the parse tree of a HAML document.
5
+ # the parse tree of a Haml document.
6
6
  module HamlVisitor
7
7
  def visit(node)
8
8
  # Keep track of whether this block was consumed by the visitor. This
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HamlLint
4
- # Contains information about a problem or issue with a HAML document.
4
+ # Contains information about a problem or issue with a Haml document.
5
5
  class Lint
6
6
  # @return [Boolean] If the error was corrected by auto-correct
7
7
  attr_reader :corrected
8
8
 
9
+ # @return [Boolean] If the error could be corrected by auto-correct
10
+ attr_reader :correctable
11
+
9
12
  # @return [String] file path to which the lint applies
10
13
  attr_reader :filename
11
14
 
@@ -28,13 +31,14 @@ module HamlLint
28
31
  # @param line [Fixnum]
29
32
  # @param message [String]
30
33
  # @param severity [Symbol]
31
- def initialize(linter, filename, line, message, severity = :warning, corrected: false) # rubocop:disable Metrics/ParameterLists
34
+ def initialize(linter, filename, line, message, severity = :warning, corrected: false, correctable: false) # rubocop:disable Metrics/ParameterLists
32
35
  @linter = linter
33
36
  @filename = filename
34
37
  @line = line || 0
35
38
  @message = message
36
39
  @severity = Severity.new(severity)
37
40
  @corrected = corrected
41
+ @correctable = correctable
38
42
  end
39
43
 
40
44
  # Return whether this lint has a severity of error.
@@ -45,7 +49,8 @@ module HamlLint
45
49
  end
46
50
 
47
51
  def inspect
48
- "#{self.class.name}(corrected=#{corrected}, filename=#{filename}, line=#{line}, " \
52
+ "#{self.class.name}(corrected=#{corrected}, correctable=#{correctable}, " \
53
+ "filename=#{filename}, line=#{line}, " \
49
54
  "linter=#{linter.class.name}, message=#{message}, severity=#{severity})"
50
55
  end
51
56
  end
@@ -3,12 +3,29 @@
3
3
  module HamlLint
4
4
  # Checks for tabs that are placed for alignment of tag content
5
5
  class Linter::AlignmentTabs < Linter
6
+ include LinterRegistry
7
+
8
+ supports_autocorrect(true)
9
+ autocorrect_safe(false)
10
+
6
11
  REGEX = /[^\s*]\t+/
12
+ # Same as +REGEX+, but captures the character preceding the alignment tabs
13
+ # so it can be kept while the tabs themselves are collapsed to a space.
14
+ CORRECTION_REGEX = /([^\s*])\t+/
7
15
 
8
16
  def visit_tag(node)
9
- if REGEX.match?(node.source_code)
10
- record_lint(node, 'Avoid using tabs for alignment')
11
- end
17
+ return unless REGEX.match?(node.source_code)
18
+
19
+ record_lint(node, 'Avoid using tabs for alignment', corrected: correct(node))
20
+ end
21
+
22
+ private
23
+
24
+ # @return [Boolean] whether a correction was applied
25
+ def correct(node)
26
+ index = node.line - 1
27
+ line = autocorrected_lines[index]
28
+ correct_line(index, line.gsub(CORRECTION_REGEX) { "#{::Regexp.last_match(1)} " })
12
29
  end
13
30
  end
14
31
  end
@@ -6,6 +6,9 @@ module HamlLint
6
6
  class Linter::ConsecutiveSilentScripts < Linter
7
7
  include LinterRegistry
8
8
 
9
+ supports_autocorrect(true)
10
+ autocorrect_safe(false)
11
+
9
12
  SILENT_SCRIPT_DETECTOR = ->(child) do
10
13
  child.type == :silent_script && child.children.empty?
11
14
  end
@@ -18,12 +21,21 @@ module HamlLint
18
21
  SILENT_SCRIPT_DETECTOR,
19
22
  config['max_consecutive'] + 1,
20
23
  ) do |group|
24
+ reported_nodes.concat(group)
21
25
  record_lint(group.first,
22
26
  "#{group.count} consecutive Ruby scripts can be merged " \
23
- 'into a single `:ruby` filter')
27
+ 'into a single `:ruby` filter',
28
+ corrected: collect_merge(group))
24
29
  end
25
30
  end
26
31
 
32
+ def after_visit_root(_node)
33
+ super
34
+ return if merges.empty?
35
+
36
+ apply_autocorrect(merged_source)
37
+ end
38
+
27
39
  private
28
40
 
29
41
  def possible_group(node)
@@ -37,5 +49,60 @@ module HamlLint
37
49
  def reported_nodes
38
50
  @reported_nodes ||= []
39
51
  end
52
+
53
+ def reset_autocorrect_state
54
+ super
55
+ @merges = []
56
+ end
57
+
58
+ # Queues a group of consecutive silent scripts to be replaced by an
59
+ # equivalent `:ruby` filter block. Skips (and reports without correcting)
60
+ # groups that aren't a run of single-line scripts on contiguous lines.
61
+ #
62
+ # @return [Boolean] whether a correction was queued
63
+ def collect_merge(group)
64
+ return false unless autocorrect?
65
+ return false unless mergeable?(group)
66
+
67
+ merges << {
68
+ start: group.first.line - 1,
69
+ finish: group.last.line - 1,
70
+ lines: ruby_filter_lines(group),
71
+ }
72
+ true
73
+ end
74
+
75
+ # @return [Boolean] whether every script is a single line and the group
76
+ # occupies a contiguous run of lines
77
+ def mergeable?(group)
78
+ group.none? { |node| node.script.include?("\n") } &&
79
+ group.each_cons(2).all? { |a, b| b.line == a.line + 1 }
80
+ end
81
+
82
+ # Builds the replacement lines: a `:ruby` filter marker at the group's
83
+ # indentation, followed by each script's body indented one level deeper.
84
+ #
85
+ # @return [Array<String>]
86
+ def ruby_filter_lines(group)
87
+ indent = document.source_lines[group.first.line - 1][/\A\s*/]
88
+ inner = indent + (indent.include?("\t") ? "\t" : ' ')
89
+ ["#{indent}:ruby", *group.map { |node| "#{inner}#{node.script.strip}" }]
90
+ end
91
+
92
+ # Applies the queued range replacements from the bottom up (so earlier line
93
+ # indices stay valid) and returns the rebuilt source.
94
+ #
95
+ # @return [String]
96
+ def merged_source
97
+ lines = document.source_lines.dup
98
+ merges.sort_by { |merge| -merge[:start] }.each do |merge|
99
+ lines[merge[:start]..merge[:finish]] = merge[:lines]
100
+ end
101
+ lines.join("\n")
102
+ end
103
+
104
+ def merges
105
+ @merges ||= []
106
+ end
40
107
  end
41
108
  end
@@ -6,11 +6,80 @@ module HamlLint
6
6
  class Linter::HtmlAttributes < Linter
7
7
  include LinterRegistry
8
8
 
9
+ supports_autocorrect(true)
10
+ autocorrect_safe(false)
11
+
12
+ MESSAGE = "Prefer the hash attributes syntax (%tag{ lang: 'en' }) " \
13
+ 'over HTML attributes syntax (%tag(lang=en))'
14
+
9
15
  def visit_tag(node)
10
16
  return unless node.html_attributes?
11
17
 
12
- record_lint(node, "Prefer the hash attributes syntax (%tag{ lang: 'en' }) " \
13
- 'over HTML attributes syntax (%tag(lang=en))')
18
+ record_lint(node, MESSAGE, corrected: correct(node))
19
+ end
20
+
21
+ private
22
+
23
+ # Rewrites the HTML attribute group `(...)` as a Ruby hash group `{...}`,
24
+ # preserving each attribute's value (static strings, booleans, and Ruby
25
+ # expressions alike).
26
+ #
27
+ # The conversion is skipped (the lint is still reported) when it can't be
28
+ # performed losslessly: when the tag already has a `{...}` hash group, when
29
+ # the `.class`/`#id` shorthand is present (its `class`/`id` are merged into
30
+ # the parsed attributes and can't be told apart from the HTML ones), or when
31
+ # the attribute list spans multiple lines.
32
+ #
33
+ # @return [Boolean] whether a correction was applied
34
+ def correct(node)
35
+ return false unless correctable?(node)
36
+ return false unless pairs = hash_pairs(node)
37
+
38
+ hash_source = "{ #{pairs.join(', ')} }"
39
+ return false unless parse_ruby(hash_source)
40
+
41
+ index = node.line - 1
42
+ line = autocorrected_lines[index]
43
+ html_source = node.dynamic_attributes_source[:html]
44
+ return false unless line.include?(html_source)
45
+
46
+ correct_line(index, line.sub(html_source) { hash_source })
47
+ end
48
+
49
+ # Whether the tag's HTML attributes can be losslessly converted in place.
50
+ #
51
+ # @return [Boolean]
52
+ def correctable?(node)
53
+ return false if node.hash_attributes?
54
+ return false unless node.static_attributes_source.empty?
55
+
56
+ html_source = node.dynamic_attributes_source[:html]
57
+ !html_source.nil? && !html_source.include?("\n")
58
+ end
59
+
60
+ # Builds the `key => value` fragments for the tag's HTML attributes, or +nil+
61
+ # when one of the static values can't be safely serialized.
62
+ #
63
+ # @return [Array<String>, nil]
64
+ def hash_pairs(node)
65
+ pairs = node.static_attributes.map do |key, value|
66
+ return nil unless serializable?(value)
67
+
68
+ "#{key.inspect} => #{value.inspect}"
69
+ end
70
+
71
+ node.dynamic_attributes_sources.each do |literal|
72
+ inner = literal.strip.delete_prefix('{').delete_suffix('}').sub(/,\s*\z/, '').strip
73
+ pairs << inner unless inner.empty?
74
+ end
75
+
76
+ pairs
77
+ end
78
+
79
+ # @return [Boolean] whether the static attribute value round-trips through
80
+ # +#inspect+ as a Ruby literal
81
+ def serializable?(value)
82
+ value.is_a?(String) || [true, false, nil].include?(value) || value.is_a?(Numeric)
14
83
  end
15
84
  end
16
85
  end
@@ -4,14 +4,14 @@ require 'rubocop'
4
4
  require 'tempfile'
5
5
 
6
6
  module HamlLint
7
- # Runs RuboCop on the Ruby code contained within HAML templates.
7
+ # Runs RuboCop on the Ruby code contained within Haml templates.
8
8
  #
9
9
  # The processing is done by extracting a Ruby file that matches the content, including
10
- # the indentation, of the HAML file. This way, we can run RuboCop with autocorrect
11
- # and get new Ruby code which should be HAML compatible.
10
+ # the indentation, of the Haml file. This way, we can run RuboCop with autocorrect
11
+ # and get new Ruby code which should be Haml compatible.
12
12
  #
13
- # The ruby extraction makes "Chunks" which wrap each HAML constructs. The Chunks can then
14
- # use the corrected Ruby code to apply the corrections back in the HAML using logic specific
13
+ # The ruby extraction makes "Chunks" which wrap each Haml constructs. The Chunks can then
14
+ # use the corrected Ruby code to apply the corrections back in the Haml using logic specific
15
15
  # to each type of Chunk.
16
16
  #
17
17
  # The work is spread across the classes in the HamlLint::RubyExtraction module.
@@ -24,7 +24,7 @@ module HamlLint
24
24
  @offenses = []
25
25
  @config_store.instance_variable_set(:@options_config, config)
26
26
  # Using stdin also disables RuboCop's result cache, which is intentional:
27
- # it reconstructs offense positions from the on-disk HAML, not the Ruby we
27
+ # it reconstructs offense positions from the on-disk Haml, not the Ruby we
28
28
  # inspected, so reusing it misreports lines (sds/haml-lint#593).
29
29
  @options[:stdin] = ruby_code
30
30
  super([haml_path])
@@ -282,7 +282,7 @@ module HamlLint
282
282
  end
283
283
  end
284
284
  record_lint(line, offense.message, offense.severity.name,
285
- corrected: autocorrected)
285
+ corrected: autocorrected, correctable: offense.correctable?)
286
286
  end
287
287
  end
288
288
 
@@ -291,13 +291,15 @@ module HamlLint
291
291
  # @param line [#line] line number of the lint
292
292
  # @param message [String] error/warning to display to the user
293
293
  # @param severity [Symbol] RuboCop severity level for the offense
294
- def record_lint(line, message, severity, corrected:)
294
+ # @param corrected [Boolean] whether RuboCop auto-corrected the offense
295
+ # @param correctable [Boolean] whether RuboCop is able to auto-correct the offense
296
+ def record_lint(line, message, severity, corrected:, correctable: false)
295
297
  # TODO: actual handling for RuboCop's new :info severity
296
298
  return if severity == :info
297
299
 
298
300
  @lints << HamlLint::Lint.new(self, @document.file, line, message,
299
301
  SEVERITY_MAP.fetch(severity, :warning),
300
- corrected: corrected)
302
+ corrected: corrected, correctable: correctable)
301
303
  end
302
304
 
303
305
  # rubocop:disable Style/MutableConstant
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HamlLint
4
- # Checks for Ruby comments that can be written as HAML comments.
4
+ # Checks for Ruby comments that can be written as Haml comments.
5
5
  class Linter::RubyComments < Linter
6
6
  include LinterRegistry
7
7
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HamlLint
4
- # Checks for Ruby script in HAML templates with no space after the `=`/`-`.
4
+ # Checks for Ruby script in Haml templates with no space after the `=`/`-`.
5
5
  class Linter::SpaceBeforeScript < Linter
6
6
  include LinterRegistry
7
7
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HamlLint
4
- # Flags HAML's unescaped-output markers (`!=`, `!~`, and the unescaped
4
+ # Flags Haml's unescaped-output markers (`!=`, `!~`, and the unescaped
5
5
  # plain-text `!`), which bypass HTML escaping.
6
6
  #
7
7
  # Like `raw`, `html_safe`, and `h()` in Rails, these make it easy to
@@ -11,35 +11,99 @@ module HamlLint
11
11
  class Linter::UnnecessaryStringOutput < Linter
12
12
  include LinterRegistry
13
13
 
14
+ supports_autocorrect(true)
15
+ autocorrect_safe(false)
16
+
14
17
  MESSAGE = '`= "..."` should be rewritten as `...`'
15
18
 
16
19
  def visit_tag(node)
17
- if tag_has_inline_script?(node) && inline_content_is_string?(node)
18
- record_lint(node, MESSAGE)
19
- end
20
+ return unless tag_has_inline_script?(node) && inline_content_is_string?(node)
21
+
22
+ plain = safe_plain_text(node.script)
23
+ corrected = plain ? correct_tag(node, plain) : false
24
+ record_lint(node, MESSAGE, corrected: corrected)
20
25
  end
21
26
 
22
27
  def visit_script(node)
23
- # Some script nodes created by the HAML parser aren't actually script
28
+ # Some script nodes created by the Haml parser aren't actually script
24
29
  # nodes declared via the `=` marker. Check for it.
25
30
  return unless /\A\s*=/.match?(node.source_code)
31
+ return unless plain = safe_plain_text(node.script)
26
32
 
27
- if outputs_string_literal?(node)
28
- record_lint(node, MESSAGE)
29
- end
33
+ record_lint(node, MESSAGE, corrected: correct_script(node, plain))
30
34
  end
31
35
 
32
36
  private
33
37
 
34
- def outputs_string_literal?(script_node)
35
- return unless tree = parse_ruby(script_node.script)
38
+ # Rewrites `%tag= "..."` as `%tag ...`, dropping the `=` marker and the
39
+ # surrounding quotes.
40
+ #
41
+ # @return [Boolean] whether a correction was applied
42
+ def correct_tag(node, plain)
43
+ marker = node.inline_marker_source.rstrip
44
+ # Only unwrap plain escaped output (`= "..."`); leave `!=`, `~`, and the
45
+ # whitespace-removal markers (`<`/`>`) untouched, as they change rendering.
46
+ return false unless /\A=\s*["']/.match?(marker)
47
+
48
+ index = node.line - 1
49
+ line = autocorrected_lines[index]
50
+ return false unless line.rstrip.end_with?(marker)
51
+
52
+ prefix = line.rstrip[0...(line.rstrip.length - marker.length)]
53
+ correct_line(index, "#{prefix} #{plain}")
54
+ end
55
+
56
+ # Rewrites a standalone `= "..."` script as plain text, preserving the
57
+ # node's indentation.
58
+ #
59
+ # @return [Boolean] whether a correction was applied
60
+ def correct_script(node, plain)
61
+ index = node.line - 1
62
+ line = autocorrected_lines[index]
63
+ indentation = line[/\A\s*/]
64
+ correct_line(index, "#{indentation}#{plain}")
65
+ end
66
+
67
+ # Returns the Haml plain-text equivalent of a Ruby string literal script, or
68
+ # +nil+ when the script is not a string literal that can be safely unwrapped
69
+ # (i.e. unwrapping would change the rendered output).
70
+ #
71
+ # @return [String, nil]
72
+ def safe_plain_text(script)
73
+ return if script.include?("\n")
74
+ return unless tree = parse_ruby(script)
36
75
  return unless %i[str dstr].include?(tree.type)
76
+ return unless safely_unwrappable?(tree)
77
+
78
+ plain_text_from(tree)
79
+ rescue ::Parser::SyntaxError
80
+ # Gracefully ignore syntax errors, as that's managed by a different linter
81
+ nil
82
+ end
37
83
 
84
+ # Whether unwrapping the string literal into Haml plain text would preserve
85
+ # the rendered output.
86
+ #
87
+ # @return [Boolean]
88
+ def safely_unwrappable?(tree)
38
89
  !starts_with_reserved_character?(tree.children.first) &&
39
90
  !contains_escape_sequence?(tree) &&
40
91
  !contains_significant_whitespace?(tree)
41
- rescue ::Parser::SyntaxError
42
- # Gracefully ignore syntax errors, as that's managed by a different linter
92
+ end
93
+
94
+ # Reconstructs the Haml plain-text form of a string literal. Literal segments
95
+ # keep their text (with any `#{` escaped so Haml won't interpolate it), while
96
+ # interpolation segments are emitted verbatim in their `#{...}` form.
97
+ #
98
+ # @return [String]
99
+ def plain_text_from(tree)
100
+ string_segments(tree).map do |segment|
101
+ if segment.type == :str
102
+ segment.children.first.gsub('#{') { '\#{' }
103
+ else
104
+ segment.location.expression.source
105
+ end
106
+ end.join
43
107
  end
44
108
 
45
109
  # Returns whether a string starts with a character that would otherwise be
@@ -57,7 +121,7 @@ module HamlLint
57
121
 
58
122
  # Returns whether any literal portion of the string contains a backslash
59
123
  # escape (e.g. `\n`, `\t`, `\u202F`). Such escapes are interpreted inside
60
- # a Ruby string but would be emitted verbatim as HAML plain text, so the
124
+ # a Ruby string but would be emitted verbatim as Haml plain text, so the
61
125
  # `= "..."` form is not equivalent to the unwrapped plain text.
62
126
  def contains_escape_sequence?(tree)
63
127
  string_segments(tree).any? do |segment|
@@ -65,7 +129,7 @@ module HamlLint
65
129
  end
66
130
  end
67
131
 
68
- # Returns whether the string begins or ends with whitespace. HAML strips
132
+ # Returns whether the string begins or ends with whitespace. Haml strips
69
133
  # trailing whitespace from plain text (and leading whitespace denotes
70
134
  # indentation), so unwrapping such a string would change the output.
71
135
  def contains_significant_whitespace?(tree)
@@ -56,7 +56,7 @@ module HamlLint
56
56
  end
57
57
 
58
58
  # Runs the linter against the given Haml document, raises if the file cannot be processed due to
59
- # Syntax or HAML-Lint internal errors. (For testing purposes)
59
+ # Syntax or Haml-Lint internal errors. (For testing purposes)
60
60
  #
61
61
  # @param document [HamlLint::Document]
62
62
  def run_or_raise(document, autocorrect: nil)
@@ -164,7 +164,7 @@ module HamlLint
164
164
  # mutation path (`Document#change_source`), but only when the safety gate permits.
165
165
  # No-ops otherwise; `change_source` itself also no-ops when the source is unchanged.
166
166
  #
167
- # @param new_source [String] the corrected HAML source
167
+ # @param new_source [String] the corrected Haml source
168
168
  def apply_autocorrect(new_source)
169
169
  return unless autocorrect?
170
170
  document.change_source(new_source)
@@ -212,11 +212,14 @@ module HamlLint
212
212
  #
213
213
  # @param node_or_line [#line] line number or node to extract the line number from
214
214
  # @param message [String] error/warning to display to the user
215
- def record_lint(node_or_line, message, corrected: false)
215
+ # @param corrected [Boolean] whether the lint was fixed by auto-correct
216
+ # @param correctable [Boolean] whether the lint can be fixed by auto-correct;
217
+ # defaults to whether this linter supports auto-correct at all
218
+ def record_lint(node_or_line, message, corrected: false, correctable: supports_autocorrect?)
216
219
  line = node_or_line.is_a?(Integer) ? node_or_line : node_or_line.line
217
220
  @lints << HamlLint::Lint.new(self, @document.file, line, message,
218
221
  config.fetch('severity', :warning).to_sym,
219
- corrected: corrected)
222
+ corrected: corrected, correctable: correctable)
220
223
  end
221
224
 
222
225
  # Parse Ruby code into an abstract syntax tree.
@@ -4,7 +4,7 @@ module HamlLint
4
4
  # Responsible for transforming {Haml::Parser::ParseNode} objects into
5
5
  # corresponding {HamlLint::Tree::Node} objects.
6
6
  #
7
- # The parse tree generated by HAML has a number of strange cases where certain
7
+ # The parse tree generated by Haml has a number of strange cases where certain
8
8
  # types of nodes are created that don't necessarily correspond to what one
9
9
  # would expect. This class is intended to isolate and handle these cases so
10
10
  # that linters don't have to deal with them.
@@ -16,7 +16,7 @@ module HamlLint
16
16
  @document = document
17
17
  end
18
18
 
19
- # Converts the given HAML parse node into its corresponding HAML-Lint parse
19
+ # Converts the given Haml parse node into its corresponding Haml-Lint parse
20
20
  # node.
21
21
  #
22
22
  # @param haml_node [Haml::Parser::ParseNode]
@@ -47,6 +47,8 @@ module HamlLint
47
47
  {
48
48
  severity: offense.severity,
49
49
  message: offense.message,
50
+ corrected: offense.corrected,
51
+ correctable: offense.correctable,
50
52
  location: {
51
53
  line: offense.line,
52
54
  },
@@ -58,6 +58,8 @@ module HamlLint
58
58
  def print_message(lint)
59
59
  if lint.corrected
60
60
  log.success('[Corrected] ', false)
61
+ elsif lint.correctable
62
+ log.info('[Correctable] ', false)
61
63
  end
62
64
 
63
65
  if lint.linter
@@ -2,13 +2,13 @@
2
2
 
3
3
  module HamlLint::RubyExtraction
4
4
  # This is the base class for all of the Chunks of HamlLint::RubyExtraction.
5
- # A Chunk represents a part of the HAML file that HamlLint::Linter::RuboCop
5
+ # A Chunk represents a part of the Haml file that HamlLint::Linter::RuboCop
6
6
  # is processing and will insert some Ruby code in a file passed to RuboCop.
7
7
  #
8
- # There are chunks for most HAML concepts, even if they don't represent Ruby
8
+ # There are chunks for most Haml concepts, even if they don't represent Ruby
9
9
  # code. For example, there is a chunk that represents a `%div` tag, which
10
10
  # uses a `begin` in the generated Ruby to add indentation for the children
11
- # of the %div in the Ruby file just like there is in the HAML file.
11
+ # of the %div in the Ruby file just like there is in the Haml file.
12
12
  class BaseChunk
13
13
  COMMA_CHANGES_LINES = true
14
14
 
@@ -4,7 +4,7 @@
4
4
  module HamlLint::RubyExtraction
5
5
  # Extracts "chunks" of the haml file into instances of subclasses of HamlLint::RubyExtraction::BaseChunk.
6
6
  #
7
- # This is the first step of generating Ruby code from a HAML file to then be processed by RuboCop.
7
+ # This is the first step of generating Ruby code from a Haml file to then be processed by RuboCop.
8
8
  # See HamlLint::RubyExtraction::BaseChunk for more details.
9
9
  class ChunkExtractor
10
10
  include HamlLint::HamlVisitor
@@ -17,7 +17,7 @@ module HamlLint::RubyExtraction
17
17
  ::Haml::Parser.new('', {})
18
18
  end
19
19
 
20
- # HAML strips newlines when handling multi-line statements (using pipes or trailing comma)
20
+ # Haml strips newlines when handling multi-line statements (using pipes or trailing comma)
21
21
  # We don't. So the regex must be fixed to correctly detect the start of the string.
22
22
  BLOCK_KEYWORD_REGEX = Regexp.new(Haml::Parser::BLOCK_KEYWORD_REGEX.source.sub('^', '\A'))
23
23
 
@@ -124,7 +124,7 @@ module HamlLint::RubyExtraction
124
124
  def visit_script(node, &block)
125
125
  raw_first_line = @original_haml_lines[node.line - 1]
126
126
 
127
- # ==, !, !==, &, &== means interpolation (was needed before HAML 2.2... it's still supported)
127
+ # ==, !, !==, &, &== means interpolation (was needed before Haml 2.2... it's still supported)
128
128
  # =, !=, &= mean actual ruby code is coming
129
129
  # Anything else is interpolation
130
130
  # The regex lists the case for Ruby Code. The 3 cases and making sure they are not followed by another = sign
@@ -169,20 +169,20 @@ module HamlLint::RubyExtraction
169
169
  prev_chunk.node == node.parent
170
170
  # When an outputting script is nested under another outputting script,
171
171
  # we want to block them from being merged together by rubocop, because
172
- # this doesn't make sense in HAML.
172
+ # this doesn't make sense in Haml.
173
173
  # Example:
174
174
  # = if this_is_short
175
175
  # = this_is_short_too
176
176
  # Could become (after RuboCop):
177
177
  # HL.out = (HL.out = this_is_short_too if this_is_short)
178
- # Or in (broken) HAML style:
178
+ # Or in (broken) Haml style:
179
179
  # = this_is_short_too = if this_is_short
180
180
  # By forcing this to start a chunk, there will be extra placeholders which
181
181
  # blocks rubocop from merging the lines.
182
182
  must_start_chunk = true
183
183
  elsif script_prefix != '='
184
184
  # In the few cases where &= and != are used to start the script,
185
- # We need to remember and put it back in the final HAML. Fusing scripts together
185
+ # We need to remember and put it back in the final Haml. Fusing scripts together
186
186
  # would make that basically impossible. Instead, a script has a "first_output_prefix"
187
187
  # field for this specific case
188
188
  must_start_chunk = true
@@ -301,7 +301,7 @@ module HamlLint::RubyExtraction
301
301
 
302
302
  if attributes_code&.start_with?('{')
303
303
  # HTML-style (parens) attributes, e.g. %div(foo=foo), arrive as a synthesized hash string
304
- # like '{"foo" => foo,}'. That text isn't present verbatim in the HAML, so we can't map
304
+ # like '{"foo" => foo,}'. That text isn't present verbatim in the Haml, so we can't map
305
305
  # RuboCop corrections back and intentionally don't autocorrect it. We still extract the
306
306
  # attribute *values* as a non-correctable chunk so RuboCop sees variables used here;
307
307
  # otherwise they look unused (false Lint/UselessAssignment, plus unsafe autocorrect that
@@ -406,7 +406,7 @@ module HamlLint::RubyExtraction
406
406
  end
407
407
  end
408
408
 
409
- # Visiting a HAML filter. Lines looking like ` :javascript` and the following lines
409
+ # Visiting a Haml filter. Lines looking like ` :javascript` and the following lines
410
410
  # that are nested.
411
411
  def visit_filter(node)
412
412
  # For unknown reasons, haml doesn't escape interpolations in filters.
@@ -521,14 +521,14 @@ module HamlLint::RubyExtraction
521
521
  # The first and last lines may not be the complete lines from the Haml, only the Ruby parts
522
522
  # and the indentation between the first and last list.
523
523
 
524
- # HAML transforms the ruby code in many ways as it parses a document. Often removing lines and/or
524
+ # Haml transforms the ruby code in many ways as it parses a document. Often removing lines and/or
525
525
  # indentation. This is quite annoying for us since we want the exact layout of the code to analyze it.
526
526
  #
527
527
  # This function receives the code as haml provides it and the line where it starts. It returns
528
528
  # the actual code as it is in the haml file, keeping breaks and indentation for the following lines.
529
529
  # In addition, the start position of the code in the first line.
530
530
  #
531
- # The rules for handling multiline code in HAML are as follow:
531
+ # The rules for handling multiline code in Haml are as follow:
532
532
  # * if the line being processed ends with a space and a pipe, then append to the line (without
533
533
  # newlines) every following lines that also end with a space and a pipe. This means the last line of
534
534
  # the "block" also needs a pipe at the end.
@@ -727,7 +727,7 @@ module HamlLint::RubyExtraction
727
727
 
728
728
  LOOP_KEYWORDS = %w[for until while].freeze
729
729
  def self.block_keyword(code)
730
- # Need to handle 'for'/'while' since regex stolen from HAML parser doesn't
730
+ # Need to handle 'for'/'while' since regex stolen from Haml parser doesn't
731
731
  if (keyword = code[/\A\s*([^\s]+)\s+/, 1]) && LOOP_KEYWORDS.include?(keyword)
732
732
  return keyword
733
733
  end
@@ -9,7 +9,7 @@ module HamlLint::RubyExtraction
9
9
  # * Preprocess the chunks to cleanup/fuse some of them.
10
10
  # * Generates the extracted ruby code from the Chunks.
11
11
  # * Handles the markers (see below)
12
- # * Use the chunks to transfer corrections from corrected Ruby code back to HAML
12
+ # * Use the chunks to transfer corrections from corrected Ruby code back to Haml
13
13
  #
14
14
  # The generated Ruby code uses markers to wrap around the Ruby code from the chunks.
15
15
  # Those markers look like function calls, like: `haml_lint_marker_1`, so are valid ruby.
@@ -22,7 +22,7 @@ module HamlLint::RubyExtraction
22
22
  # @return [String] The prefix used for markers in the Ruby code
23
23
  attr_reader :marker_prefix
24
24
 
25
- # @return [Array<String>] The ruby lines after extraction from HAML (before RuboCop)
25
+ # @return [Array<String>] The ruby lines after extraction from Haml (before RuboCop)
26
26
  attr_reader :assembled_ruby_lines
27
27
 
28
28
  # @return [Array<String>] The ruby lines after correction by RuboCop
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HamlLint::RubyExtraction
4
- # HAML adds a `end` when code gets outdented. We need to add that to the Ruby too, this
4
+ # Haml adds a `end` when code gets outdented. We need to add that to the Ruby too, this
5
5
  # is the chunk for it.
6
6
  # However:
7
7
  # * we can't apply fixes to it, so there are no markers
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HamlLint::RubyExtraction
4
- # Chunk for dealing with every HAML filter other than `:ruby`
4
+ # Chunk for dealing with every Haml filter other than `:ruby`
5
5
  # The generated Ruby for these is just a HEREDOC, so interpolation is corrected at
6
6
  # the same time by RuboCop.
7
7
  class NonRubyFilterChunk < BaseChunk
@@ -5,7 +5,7 @@ module HamlLint::RubyExtraction
5
5
  # transfer the corrections it receives to the indentation of the associated lines.
6
6
  #
7
7
  # Also used so that Rubocop doesn't think that there is nothing in `if` and other such structures,
8
- # so that it does corrections that make sense for the HAML.
8
+ # so that it does corrections that make sense for the Haml.
9
9
  class PlaceholderMarkerChunk < BaseChunk
10
10
  def initialize(node, marker_name, indent:, nb_lines: 1, **kwargs)
11
11
  @marker_name = marker_name
@@ -18,7 +18,7 @@ module HamlLint::RubyExtraction
18
18
  attr_reader :must_start_chunk
19
19
 
20
20
  # @return [Array<Integer>] Line indexes to ignore when building the source_map. For examples,
21
- # implicit `end` are on their own line in the Ruby file, but in the HAML, they are absent.
21
+ # implicit `end` are on their own line in the Ruby file, but in the Haml, they are absent.
22
22
  attr_reader :skip_line_indexes_in_source_map
23
23
 
24
24
  # @return [HamlLint::RubyExtraction::BaseChunk] The previous chunk can affect how
@@ -62,6 +62,8 @@ module HamlLint
62
62
  # @return [HamlLint::LinterSelector]
63
63
  attr_reader :linter_selector
64
64
 
65
+ PARALLEL_LINTER_SELECTOR_THREAD_KEY = :haml_lint_runner_parallel_linter_selectors
66
+
65
67
  # Returns a fresh selector for this run.
66
68
  #
67
69
  # LinterSelector memoizes linter instances, and linters mutate instance
@@ -73,6 +75,18 @@ module HamlLint
73
75
  HamlLint::LinterSelector.new(config, @options)
74
76
  end
75
77
 
78
+ # Returns a selector scoped to the current parallel worker.
79
+ #
80
+ # MRI runs Parallel.map in forked processes, so each worker can safely reuse
81
+ # its own linter instances. JRuby runs Parallel.map in threads, so this must
82
+ # be isolated per thread to avoid sharing mutable linter state.
83
+ #
84
+ # @return [HamlLint::LinterSelector]
85
+ def parallel_linter_selector
86
+ selectors = Thread.current[PARALLEL_LINTER_SELECTOR_THREAD_KEY] ||= {}.compare_by_identity
87
+ selectors[self] ||= build_linter_selector
88
+ end
89
+
76
90
  # Returns the {HamlLint::Configuration} that should be used given the
77
91
  # specified options.
78
92
  #
@@ -205,7 +219,7 @@ module HamlLint
205
219
  # @return [void]
206
220
  def warm_cache
207
221
  results = Parallel.map(sources) do |source|
208
- lints = collect_lints(source, build_linter_selector, config)
222
+ lints = collect_lints(source, parallel_linter_selector, config)
209
223
  [source.path, lints]
210
224
  end
211
225
  @cache = results.to_h
@@ -11,10 +11,11 @@ module HamlLint
11
11
  expected_message = options[:message]
12
12
  expected_severity = options[:severity]
13
13
  expected_corrected = options[:corrected]
14
+ expected_correctable = options[:correctable]
14
15
 
15
16
  match do |linter|
16
17
  has_lints?(linter, expected_line, count, expected_message, expected_severity,
17
- expected_corrected)
18
+ expected_corrected, expected_correctable)
18
19
  end
19
20
 
20
21
  failure_message do |linter|
@@ -65,15 +66,16 @@ module HamlLint
65
66
  (expected_severity ? " with severity '#{expected_severity}'" : '')
66
67
  end
67
68
 
68
- def has_lints?(linter, expected_line, count, expected_message, expected_severity, # rubocop:disable Metrics/ParameterLists
69
- expected_corrected)
69
+ def has_lints?(linter, expected_line, count, expected_message, expected_severity, # rubocop:disable Metrics/ParameterLists,Naming
70
+ expected_corrected, expected_correctable)
70
71
  if expected_line
71
72
  has_expected_line_lints?(linter,
72
73
  expected_line,
73
74
  count,
74
75
  expected_message,
75
76
  expected_severity,
76
- expected_corrected)
77
+ expected_corrected,
78
+ expected_correctable)
77
79
  elsif count
78
80
  linter.lints.count == count
79
81
  elsif expected_message
@@ -83,12 +85,13 @@ module HamlLint
83
85
  end
84
86
  end
85
87
 
86
- def has_expected_line_lints?(linter, # rubocop:disable Metrics/ParameterLists
88
+ def has_expected_line_lints?(linter, # rubocop:disable Metrics/ParameterLists,Naming
87
89
  expected_line,
88
90
  count,
89
91
  expected_message,
90
92
  expected_severity,
91
- expected_corrected)
93
+ expected_corrected,
94
+ expected_correctable)
92
95
  if count
93
96
  multiple_lints_match_line?(linter, expected_line, count)
94
97
  elsif expected_message
@@ -97,6 +100,8 @@ module HamlLint
97
100
  lint_on_line_matches_severity?(linter, expected_line, expected_severity)
98
101
  elsif !expected_corrected.nil?
99
102
  lint_on_line_matches_corrected?(linter, expected_line, expected_corrected)
103
+ elsif !expected_correctable.nil?
104
+ lint_on_line_matches_correctable?(linter, expected_line, expected_correctable)
100
105
  else
101
106
  lint_lines(linter).include?(expected_line)
102
107
  end
@@ -126,6 +131,12 @@ module HamlLint
126
131
  .any? { |lint| lint.line == expected_line && lint.corrected == expected_corrected }
127
132
  end
128
133
 
134
+ def lint_on_line_matches_correctable?(linter, expected_line, expected_correctable)
135
+ linter
136
+ .lints
137
+ .any? { |lint| lint.line == expected_line && lint.correctable == expected_correctable }
138
+ end
139
+
129
140
  def lint_messages_match?(linter, expected_message)
130
141
  # Using === to support regex to match anywhere in the string
131
142
  lint_messages(linter).all? { |message| expected_message === message } # rubocop:disable Style/CaseEquality
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HamlLint::Tree
4
- # Represents a visible XHTML comment in a HAML document.
4
+ # Represents a visible XHTML comment in a Haml document.
5
5
  class CommentNode < Node
6
6
  end
7
7
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HamlLint::Tree
4
- # Represents a doctype definition for a HAML document.
4
+ # Represents a doctype definition for a Haml document.
5
5
  class DoctypeNode < Node
6
6
  end
7
7
  end
@@ -9,7 +9,7 @@ module HamlLint::Tree
9
9
  end
10
10
 
11
11
  def text
12
- # Seems HAML strips the starting blank lines... without them, line numbers become offset,
12
+ # Seems Haml strips the starting blank lines... without them, line numbers become offset,
13
13
  # breaking the source_map and auto-correct
14
14
 
15
15
  nb_blank_lines = 0
@@ -3,7 +3,7 @@
3
3
  require_relative '../directive'
4
4
 
5
5
  module HamlLint::Tree
6
- # Represents a HAML comment node.
6
+ # Represents a Haml comment node.
7
7
  class HamlCommentNode < Node
8
8
  def directives
9
9
  directives = super
@@ -28,7 +28,7 @@ module HamlLint::Tree
28
28
  # Returns whether this comment contains a `locals` directive.
29
29
  #
30
30
  # @return [Boolean]
31
- def is_strict_locals?
31
+ def is_strict_locals? # rubocop:disable Naming
32
32
  text.lstrip.start_with?('locals:')
33
33
  end
34
34
 
@@ -3,13 +3,13 @@
3
3
  require_relative '../comment_configuration'
4
4
 
5
5
  module HamlLint::Tree
6
- # Decorator class that provides a convenient set of helpers for HAML's
6
+ # Decorator class that provides a convenient set of helpers for Haml's
7
7
  # {Haml::Parser::ParseNode} struct.
8
8
  #
9
9
  # The goal is to abstract away the details of the underlying struct and
10
10
  # provide a cleaner and more uniform interface for getting information about a
11
11
  # node, as there are a number of weird/special cases in the struct returned by
12
- # the HAML parser.
12
+ # the Haml parser.
13
13
  #
14
14
  # @abstract
15
15
  class Node
@@ -21,7 +21,7 @@ module HamlLint::Tree
21
21
  # Creates a node wrapping the given {Haml::Parser::ParseNode} struct.
22
22
  #
23
23
  # @param document [HamlLint::Document] Haml document that created this node
24
- # @param parse_node [Haml::Parser::ParseNode] parse node created by HAML's parser
24
+ # @param parse_node [Haml::Parser::ParseNode] parse node created by Haml's parser
25
25
  def initialize(document, parse_node)
26
26
  @line = parse_node.line
27
27
  @document = document
@@ -3,7 +3,7 @@
3
3
  require_relative 'null_node'
4
4
 
5
5
  module HamlLint::Tree
6
- # Represents the root node of a HAML document that contains all other nodes.
6
+ # Represents the root node of a Haml document that contains all other nodes.
7
7
  class RootNode < Node
8
8
  # The name of the file parsed to build this tree.
9
9
  #
@@ -21,7 +21,7 @@ module HamlLint::Tree
21
21
  return node if node.line_numbers.cover?(line) && node != self
22
22
  end
23
23
 
24
- # Because HAML doesn't leave any trace in the nodes when it merges lines that
24
+ # Because Haml doesn't leave any trace in the nodes when it merges lines that
25
25
  # end with a comma, it's harder to assign a node to the second line here:
26
26
  # = some_call user,
27
27
  # foo, bar
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HamlLint::Tree
4
- # Represents a HAML silent script node (`- some_expression`) which executes
4
+ # Represents a Haml silent script node (`- some_expression`) which executes
5
5
  # code without producing output.
6
6
  class SilentScriptNode < Node
7
7
  # The Ruby script contents parsed into a syntax tree.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HamlLint::Tree
4
- # Represents a tag node in a HAML document.
4
+ # Represents a tag node in a Haml document.
5
5
  class TagNode < Node
6
6
  # Computed set of attribute hashes code.
7
7
  #
@@ -46,7 +46,7 @@ module HamlLint::Tree
46
46
  # Returns whether this tag has a specified attribute.
47
47
  #
48
48
  # @return [true,false]
49
- def has_hash_attribute?(attribute)
49
+ def has_hash_attribute?(attribute) # rubocop:disable Naming
50
50
  hash_attributes? && existing_attributes.include?(attribute)
51
51
  end
52
52
 
@@ -155,6 +155,18 @@ module HamlLint::Tree
155
155
  dynamic_attributes_source[:html][/\A\((.*)\)\z/, 1] if html_attributes?
156
156
  end
157
157
 
158
+ # The tag's static (non-Ruby) attributes parsed into a Ruby hash. Keys are
159
+ # strings and values are their literal parsed values (e.g. `true` for a
160
+ # boolean attribute such as `(required)`).
161
+ #
162
+ # @note When the tag uses the `.class`/`#id` shorthand, the resulting
163
+ # `class`/`id` entries are merged into this hash as well.
164
+ #
165
+ # @return [Hash]
166
+ def static_attributes
167
+ @value[:attributes]
168
+ end
169
+
158
170
  # ID of the HTML tag.
159
171
  #
160
172
  # @return [String]
@@ -233,7 +233,7 @@ module HamlLint
233
233
 
234
234
  # Returns true if line is only whitespace.
235
235
  # Note, this is not like blank? is rails. For nil, this returns false.
236
- def is_blank_line?(line)
236
+ def is_blank_line?(line) # rubocop:disable Naming
237
237
  line && line.index(/\S/).nil?
238
238
  end
239
239
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Defines the gem version.
4
4
  module HamlLint
5
- VERSION = '0.74.0'
5
+ VERSION = '0.76.0'
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: haml_lint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.74.0
4
+ version: 0.76.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shane da Silva