haml_lint 0.53.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +17 -5
  3. data/config/forced_rubocop_config.yml +25 -13
  4. data/lib/haml_lint/adapter.rb +5 -4
  5. data/lib/haml_lint/cli.rb +7 -3
  6. data/lib/haml_lint/constants.rb +1 -1
  7. data/lib/haml_lint/directive.rb +2 -2
  8. data/lib/haml_lint/document.rb +14 -5
  9. data/lib/haml_lint/exceptions.rb +2 -2
  10. data/lib/haml_lint/extensions/haml_util_unescape_interpolation_tracking.rb +2 -2
  11. data/lib/haml_lint/haml_visitor.rb +1 -1
  12. data/lib/haml_lint/lint.rb +8 -3
  13. data/lib/haml_lint/linter/alignment_tabs.rb +21 -4
  14. data/lib/haml_lint/linter/class_attribute_with_static_value.rb +52 -12
  15. data/lib/haml_lint/linter/classes_before_ids.rb +15 -6
  16. data/lib/haml_lint/linter/consecutive_comments.rb +20 -1
  17. data/lib/haml_lint/linter/consecutive_silent_scripts.rb +68 -1
  18. data/lib/haml_lint/linter/empty_object_reference.rb +16 -1
  19. data/lib/haml_lint/linter/empty_script.rb +32 -2
  20. data/lib/haml_lint/linter/final_newline.rb +31 -7
  21. data/lib/haml_lint/linter/html_attributes.rb +71 -2
  22. data/lib/haml_lint/linter/id_names.rb +1 -1
  23. data/lib/haml_lint/linter/implicit_div.rb +14 -1
  24. data/lib/haml_lint/linter/indentation.rb +2 -2
  25. data/lib/haml_lint/linter/instance_variables.rb +28 -3
  26. data/lib/haml_lint/linter/leading_comment_space.rb +14 -2
  27. data/lib/haml_lint/linter/multiline_pipe.rb +3 -3
  28. data/lib/haml_lint/linter/multiline_script.rb +65 -5
  29. data/lib/haml_lint/linter/no_placeholders.rb +2 -2
  30. data/lib/haml_lint/linter/rubocop.rb +87 -76
  31. data/lib/haml_lint/linter/ruby_comments.rb +14 -4
  32. data/lib/haml_lint/linter/space_before_script.rb +37 -4
  33. data/lib/haml_lint/linter/space_inside_hash_attributes.rb +41 -3
  34. data/lib/haml_lint/linter/strict_locals.rb +69 -0
  35. data/lib/haml_lint/linter/tag_name.rb +15 -2
  36. data/lib/haml_lint/linter/trailing_empty_lines.rb +13 -1
  37. data/lib/haml_lint/linter/trailing_whitespace.rb +16 -3
  38. data/lib/haml_lint/linter/unescaped_html.rb +27 -0
  39. data/lib/haml_lint/linter/unnecessary_interpolation.rb +30 -3
  40. data/lib/haml_lint/linter/unnecessary_string_output.rb +108 -12
  41. data/lib/haml_lint/linter.rb +119 -5
  42. data/lib/haml_lint/node_transformer.rb +2 -2
  43. data/lib/haml_lint/options.rb +17 -3
  44. data/lib/haml_lint/rake_task.rb +3 -2
  45. data/lib/haml_lint/reporter/default_reporter.rb +1 -1
  46. data/lib/haml_lint/reporter/disabled_config_reporter.rb +29 -8
  47. data/lib/haml_lint/reporter/github_reporter.rb +50 -0
  48. data/lib/haml_lint/reporter/hash_reporter.rb +2 -0
  49. data/lib/haml_lint/reporter/json_reporter.rb +1 -1
  50. data/lib/haml_lint/reporter/progress_reporter.rb +2 -1
  51. data/lib/haml_lint/reporter/utils.rb +4 -2
  52. data/lib/haml_lint/reporter.rb +1 -1
  53. data/lib/haml_lint/ruby_extraction/base_chunk.rb +4 -4
  54. data/lib/haml_lint/ruby_extraction/chunk_extractor.rb +73 -32
  55. data/lib/haml_lint/ruby_extraction/coordinator.rb +9 -4
  56. data/lib/haml_lint/ruby_extraction/implicit_end_chunk.rb +1 -1
  57. data/lib/haml_lint/ruby_extraction/non_ruby_filter_chunk.rb +2 -2
  58. data/lib/haml_lint/ruby_extraction/placeholder_marker_chunk.rb +1 -1
  59. data/lib/haml_lint/ruby_extraction/ruby_filter_chunk.rb +1 -1
  60. data/lib/haml_lint/ruby_extraction/script_chunk.rb +2 -2
  61. data/lib/haml_lint/runner.rb +70 -30
  62. data/lib/haml_lint/source.rb +25 -0
  63. data/lib/haml_lint/spec/matchers/report_lint.rb +17 -6
  64. data/lib/haml_lint/spec/shared_rubocop_autocorrect_context.rb +34 -4
  65. data/lib/haml_lint/spec.rb +4 -4
  66. data/lib/haml_lint/tree/comment_node.rb +1 -1
  67. data/lib/haml_lint/tree/doctype_node.rb +1 -1
  68. data/lib/haml_lint/tree/filter_node.rb +14 -1
  69. data/lib/haml_lint/tree/haml_comment_node.rb +9 -2
  70. data/lib/haml_lint/tree/node.rb +4 -4
  71. data/lib/haml_lint/tree/root_node.rb +4 -4
  72. data/lib/haml_lint/tree/script_node.rb +1 -1
  73. data/lib/haml_lint/tree/silent_script_node.rb +1 -1
  74. data/lib/haml_lint/tree/tag_node.rb +69 -25
  75. data/lib/haml_lint/utils.rb +4 -31
  76. data/lib/haml_lint/version.rb +1 -1
  77. data/lib/haml_lint.rb +23 -23
  78. metadata +14 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 49bb0bec15e96c047439560e80f83eaf6f4c3baec24a12b1f10248f1d2f347d3
4
- data.tar.gz: 153f65eda76c5751bbae1d02fc49d0d4477f401bb273096ad9308868b9d21288
3
+ metadata.gz: 188328ae96f24b3a739665a5d3ccc7271a753afbe0ae63a1fbe3f33897492caa
4
+ data.tar.gz: 52b39c0992433545f610c7c18af3d06b224027edea3a9cdb97f71c7c1cf4f8c5
5
5
  SHA512:
6
- metadata.gz: a4d2c2682e7b12d9aaf8164054f3533c0667f5f6035bfbf5ef6bbb694a40f9a70d9d13a9aa56dabf27131be27e74d346c9eff642a65d0e2aac8992ebc1999fba
7
- data.tar.gz: 343aee9ed9bf4a766640aabbbaf96af9a5c36261fb6e6943b9a0b9eadcb5c75bc5a15665cae30c3c4c8925b307ecca837130fb04cf1d14a74549978db235f60c
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
 
@@ -50,8 +50,8 @@ linters:
50
50
 
51
51
  Indentation:
52
52
  enabled: true
53
- character: space # or tab
54
- width: 2 # ignored if character == tab
53
+ character: space # or tab
54
+ width: 2 # ignored if character == tab
55
55
 
56
56
  InlineStyles:
57
57
  enabled: true
@@ -88,8 +88,10 @@ linters:
88
88
 
89
89
  RuboCop:
90
90
  enabled: true
91
- # Users can ignore cops using this configuration instead of editing their rubocop configuration.
92
- # Mostly there for backward compatibility.
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,
93
+ # and the only way to disable a forced cop (see config/forced_rubocop_config.yml), since the
94
+ # forced configuration always overrides `Enabled: false` set in your own .rubocop.yml.
93
95
  ignored_cops: []
94
96
 
95
97
  RubyComments:
@@ -102,6 +104,13 @@ linters:
102
104
  enabled: true
103
105
  style: space
104
106
 
107
+ StrictLocals:
108
+ enabled: false
109
+ file_types: partials
110
+ matchers:
111
+ all: .*
112
+ partials: \A_.*\.haml\z
113
+
105
114
  TagName:
106
115
  enabled: true
107
116
 
@@ -111,6 +120,9 @@ linters:
111
120
  TrailingWhitespace:
112
121
  enabled: true
113
122
 
123
+ UnescapedHtml:
124
+ enabled: true
125
+
114
126
  UnnecessaryInterpolation:
115
127
  enabled: true
116
128
 
@@ -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,20 +31,32 @@ 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
 
48
+ # Complying with this lint requires the use of pipes which causes a conflict with MultilinePipe.
49
+ Layout/FirstArrayElementLineBreak:
50
+ Enabled: false
51
+
52
+ # Complying with this lint requires the use of pipes which causes a conflict with MultilinePipe.
53
+ Layout/FirstHashElementLineBreak:
54
+ Enabled: false
55
+
56
+ # Complying with this lint requires the use of pipes which causes a conflict with MultilinePipe.
57
+ Layout/FirstMethodArgumentLineBreak:
58
+ Enabled: false
59
+
48
60
  # Turning this cop on can turn
49
61
  # = content_tag(:span) do
50
62
  # - foo
@@ -78,14 +90,14 @@ Metrics/BlockNesting:
78
90
  Naming/FileName:
79
91
  Enabled: false
80
92
 
81
- # 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.
82
94
  # If you don't consider the { } block for indentation, things "works", but the indentation is misleading.
83
95
  # For example, this works:
84
96
  # - a = lambda {
85
97
  # - if abc
86
98
  # - something
87
99
  # - }
88
- # 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
89
101
  # ruby would be invalid.
90
102
  Style/BlockDelimiters:
91
103
  # So we need this cop to cleanup those cases and turn them to `end`.
@@ -94,7 +106,7 @@ Style/BlockDelimiters:
94
106
  # We don't allow the default "Can be anything" exception for lambda/proc
95
107
  <%= rubocop_version < '1.33' ? 'IgnoredMethods' : 'AllowedMethods' %>: []
96
108
 
97
- # We don't support correcting HAML comments
109
+ # We don't support correcting Haml comments
98
110
  Style/CommentAnnotation:
99
111
  AutoCorrect: false
100
112
 
@@ -132,7 +144,7 @@ Style/RedundantStringEscape:
132
144
  <% end %>
133
145
 
134
146
  # In some case, this cop can cause a change in the spacing.
135
- # In HAML 5.2, going from (absurd example for clarity):
147
+ # In Haml 5.2, going from (absurd example for clarity):
136
148
  # = 'abc' rescue nil
137
149
  # = 'def'
138
150
  # to:
@@ -149,7 +161,7 @@ Style/RescueModifier:
149
161
 
150
162
  # Cops that remove commas can be a problem when lines are split on multiple ones.
151
163
  # If we have a big array on more than one line, the removal of the comma generates
152
- # invalid HAML
164
+ # invalid Haml
153
165
  Style/SymbolArray:
154
166
  Enabled: false
155
167
 
@@ -158,7 +170,7 @@ Style/UnlessElse:
158
170
  AutoCorrect: false
159
171
 
160
172
  # If an array of strings was on multiple lines, this cop will make a %w(...) on multiple lines.
161
- # 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
162
174
  # case where a script can change line is after a comma.
163
175
  Style/WordArray:
164
176
  AutoCorrect: false
@@ -169,7 +181,7 @@ Layout/TrailingEmptyLines:
169
181
 
170
182
  <% if rubocop_version < '1.8.1' %>
171
183
  # There were a few bugs with this cop that got fixed in this version.
172
- # 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
173
185
  # responsible, at least from the user's point of view.
174
186
  Style/StringConcatenation:
175
187
  Enabled: false
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'haml_lint/adapter/haml_5'
4
- require 'haml_lint/adapter/haml_6'
5
- require 'haml_lint/exceptions'
3
+ require_relative 'adapter/haml_5'
4
+ require_relative 'adapter/haml_6'
5
+ require_relative 'exceptions'
6
6
 
7
7
  module HamlLint
8
8
  # Determines the adapter to use for the current Haml version
@@ -19,7 +19,8 @@ module HamlLint
19
19
  version = haml_version
20
20
  case version
21
21
  when '~> 5.0', '~> 5.1', '~> 5.2' then HamlLint::Adapter::Haml5
22
- when '~> 6.0', '~> 6.0.a', '~> 6.1', '~> 6.2', '~> 6.3' then HamlLint::Adapter::Haml6
22
+ when '~> 6.0', '~> 6.0.a', '~> 6.1', '~> 6.2', '~> 6.3', '~> 6.4' then HamlLint::Adapter::Haml6
23
+ when '~> 7.0', '~> 7.1', '~> 7.2' then HamlLint::Adapter::Haml6
23
24
  else fail HamlLint::Exceptions::UnknownHamlVersion, "Cannot handle Haml version: #{version}"
24
25
  end
25
26
  end
data/lib/haml_lint/cli.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'haml_lint'
4
- require 'haml_lint/options'
3
+ require_relative '../haml_lint'
4
+ require_relative 'options'
5
5
 
6
6
  require 'sysexits'
7
7
 
@@ -100,7 +100,11 @@ module HamlLint
100
100
  # @return [HamlLint::Reporter]
101
101
  def reporter_from_options(options)
102
102
  if options[:auto_gen_config]
103
- HamlLint::Reporter::DisabledConfigReporter.new(log, limit: options[:auto_gen_exclude_limit] || 15)
103
+ HamlLint::Reporter::DisabledConfigReporter.new(
104
+ log,
105
+ limit: options[:auto_gen_exclude_limit] || HamlLint::Reporter::DisabledConfigReporter::DEFAULT_EXCLUDE_LIMIT,
106
+ options: options
107
+ )
104
108
  else
105
109
  options.fetch(:reporter, HamlLint::Reporter::DefaultReporter).new(log)
106
110
  end
@@ -6,5 +6,5 @@ module HamlLint
6
6
  APP_NAME = 'haml-lint'
7
7
 
8
8
  REPO_URL = 'https://github.com/sds/haml-lint'
9
- BUG_REPORT_URL = "#{REPO_URL}/issues"
9
+ BUG_REPORT_URL = "#{REPO_URL}/issues".freeze
10
10
  end
@@ -3,7 +3,7 @@
3
3
  module HamlLint
4
4
  # Handles linter configuration transformation via Haml comments.
5
5
  class Directive
6
- LINTER_REGEXP = /(?:[A-Z]\w+)/.freeze
6
+ LINTER_REGEXP = /(?:[A-Z]\w+)/
7
7
 
8
8
  DIRECTIVE_REGEXP = /
9
9
  # "haml-lint:" with optional spacing
@@ -14,7 +14,7 @@ module HamlLint
14
14
 
15
15
  # "all" or a comma-separated list (with optional spaces) of linters
16
16
  (?<linters>all | (?:#{LINTER_REGEXP}\s*,\s*)* #{LINTER_REGEXP})
17
- /x.freeze
17
+ /x
18
18
 
19
19
  # Constructs a directive from source code as a given line.
20
20
  #
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'haml_lint/adapter'
3
+ require_relative 'adapter'
4
4
 
5
5
  module HamlLint
6
6
  # Represents a parsed Haml document and its associated metadata.
@@ -14,6 +14,9 @@ module HamlLint
14
14
  # @return [String] Haml template file path
15
15
  attr_reader :file
16
16
 
17
+ # @return [Boolean] true if source changes (from autocorrect) should be written to stdout instead of disk
18
+ attr_reader :write_to_stdout
19
+
17
20
  # @return [HamlLint::Tree::Node] Root of the parse tree
18
21
  attr_reader :tree
19
22
 
@@ -36,10 +39,12 @@ module HamlLint
36
39
  # @param source [String] Haml code to parse
37
40
  # @param options [Hash]
38
41
  # @option options :file [String] file name of document that was parsed
42
+ # @option options :write_to_stdout [Boolean] true if source changes should be written to stdout
39
43
  # @raise [Haml::Parser::Error] if there was a problem parsing the document
40
44
  def initialize(source, options)
41
45
  @config = options[:config]
42
46
  @file = options.fetch(:file, STRING_SOURCE)
47
+ @write_to_stdout = options[:write_to_stdout]
43
48
  @source_was_changed = false
44
49
  process_source(source)
45
50
  end
@@ -82,7 +87,11 @@ module HamlLint
82
87
  if file == STRING_SOURCE
83
88
  raise HamlLint::Exceptions::InvalidFilePath, 'Cannot write without :file option'
84
89
  end
85
- File.write(file, unstrip_frontmatter(source))
90
+ if @write_to_stdout
91
+ $stdout << unstrip_frontmatter(source)
92
+ else
93
+ File.write(file, unstrip_frontmatter(source))
94
+ end
86
95
  @source_was_changed = false
87
96
  end
88
97
 
@@ -123,8 +132,8 @@ module HamlLint
123
132
  # @param original_tree [Haml::Parser::ParseNode]
124
133
  # @return [Haml::Tree::Node]
125
134
  def process_tree(original_tree)
126
- # Remove the trailing empty HAML comment that the parser creates to signal
127
- # 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
128
137
  if Gem::Requirement.new('~> 4.0.0').satisfied_by?(Gem.loaded_specs['haml'].version)
129
138
  original_tree.children.pop
130
139
  end
@@ -133,7 +142,7 @@ module HamlLint
133
142
  convert_tree(original_tree)
134
143
  end
135
144
 
136
- # 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.
137
146
  #
138
147
  # This provides a cleaner interface with which the linters can interact with
139
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 souce, 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
- REGEX = /[^\s*]\t+/.freeze
6
+ include LinterRegistry
7
+
8
+ supports_autocorrect(true)
9
+ autocorrect_safe(false)
10
+
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
@@ -18,15 +18,19 @@ module HamlLint
18
18
  class Linter::ClassAttributeWithStaticValue < Linter
19
19
  include LinterRegistry
20
20
 
21
+ supports_autocorrect(true)
22
+
21
23
  STATIC_TYPES = %i[str sym].freeze
22
24
 
23
- VALID_CLASS_REGEX = /^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$/.freeze
25
+ VALID_CLASS_REGEX = /^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$/
24
26
 
25
27
  def visit_tag(node)
26
- return unless contains_class_attribute?(node.dynamic_attributes_sources)
28
+ class_value = static_class_value(node.dynamic_attributes_sources)
29
+ return unless class_value
27
30
 
31
+ corrected = correct_class_attribute(node, class_value)
28
32
  record_lint(node, 'Avoid defining `class` in attributes hash ' \
29
- 'for static class names')
33
+ 'for static class names', corrected: corrected)
30
34
  end
31
35
 
32
36
  private
@@ -35,28 +39,64 @@ module HamlLint
35
39
  code.start_with?('{') && code.end_with?('}')
36
40
  end
37
41
 
38
- def contains_class_attribute?(attributes_sources)
42
+ # @return [String, nil]
43
+ def static_class_value(attributes_sources)
39
44
  attributes_sources.each do |code|
40
45
  ast_root = parse_ruby(surrounded_by_braces?(code) ? code : "{#{code}}")
41
46
  next unless ast_root # RuboCop linter will report syntax errors
42
47
 
43
48
  ast_root.children.each do |pair|
44
- return true if static_class_attribute_value?(pair)
49
+ value = static_class_attribute_value(pair)
50
+ return value if value
45
51
  end
46
52
  end
47
53
 
48
- false
54
+ nil
49
55
  end
50
56
 
51
- def static_class_attribute_value?(pair)
52
- return false if (children = pair.children).empty?
57
+ # @return [String, nil]
58
+ def static_class_attribute_value(pair)
59
+ return nil if (children = pair.children).empty?
53
60
 
54
61
  key, value = children
55
62
 
56
- STATIC_TYPES.include?(key.type) &&
57
- key.children.first.to_sym == :class &&
58
- STATIC_TYPES.include?(value.type) &&
59
- value.children.first =~ VALID_CLASS_REGEX
63
+ return nil unless STATIC_TYPES.include?(key.type) &&
64
+ key.children.first.to_sym == :class &&
65
+ STATIC_TYPES.include?(value.type)
66
+
67
+ class_name = value.children.first.to_s
68
+ VALID_CLASS_REGEX.match?(class_name) ? class_name : nil
69
+ end
70
+
71
+ # @return [Boolean]
72
+ def correct_class_attribute(node, class_value)
73
+ hash_source = node.hash_attributes_source
74
+ return false unless hash_source
75
+ return false if hash_source.include?("\n")
76
+ return false unless node.dynamic_attributes_source.keys == [:hash]
77
+ return false unless sole_pair?(node.dynamic_attributes_sources)
78
+ return false if node.static_classes.flatten.include?(class_value)
79
+
80
+ index = node.line - 1
81
+ line = autocorrected_lines[index]
82
+ old = "#{node.static_attributes_source}#{hash_source}"
83
+ return false unless line.include?(old)
84
+
85
+ new = "#{node.static_attributes_source}.#{class_value}"
86
+ correct_line(index, line.sub(old, new))
87
+ end
88
+
89
+ # Whether the attribute hash contains exactly one key/value pair (the class).
90
+ #
91
+ # @return [Boolean]
92
+ def sole_pair?(attributes_sources)
93
+ return false unless attributes_sources.size == 1
94
+
95
+ code = attributes_sources.first
96
+ ast_root = parse_ruby(surrounded_by_braces?(code) ? code : "{#{code}}")
97
+ return false unless ast_root
98
+
99
+ ast_root.children.size == 1
60
100
  end
61
101
  end
62
102
  end
@@ -5,11 +5,7 @@ module HamlLint
5
5
  class Linter::ClassesBeforeIds < Linter
6
6
  include LinterRegistry
7
7
 
8
- # Map of prefixes to the type of tag component
9
- TYPES_BY_PREFIX = {
10
- '.' => :class,
11
- '#' => :id,
12
- }.freeze
8
+ supports_autocorrect(true)
13
9
 
14
10
  MSG = '%s should be listed before %s (%s should precede %s)'
15
11
 
@@ -23,14 +19,27 @@ module HamlLint
23
19
  next unless next_val.start_with?(first) &&
24
20
  current_val.start_with?(second)
25
21
 
22
+ corrected = correct_attribute_order(node, components)
26
23
  failure_message = format(MSG, *(attribute_type_order + [next_val, current_val]))
27
- record_lint(node, failure_message)
24
+ record_lint(node, failure_message, corrected: corrected)
28
25
  break
29
26
  end
30
27
  end
31
28
 
32
29
  private
33
30
 
31
+ # @param node [HamlLint::Tree::TagNode]
32
+ # @param components [Array<String>] the `.class`/`#id` components in source order
33
+ # @return [Boolean]
34
+ def correct_attribute_order(node, components)
35
+ classes, ids = components.partition { |component| component.start_with?('.') }
36
+ new_source = (ids_first? ? ids + classes : classes + ids).join
37
+
38
+ index = node.line - 1
39
+ line = autocorrected_lines[index]
40
+ correct_line(index, line.sub(node.static_attributes_source, new_source))
41
+ end
42
+
34
43
  def attribute_prefix_order
35
44
  default = %w[. #]
36
45
  default.reverse! if ids_first?
@@ -5,6 +5,9 @@ module HamlLint
5
5
  class Linter::ConsecutiveComments < Linter
6
6
  include LinterRegistry
7
7
 
8
+ supports_autocorrect(true)
9
+ autocorrect_safe(false)
10
+
8
11
  COMMENT_DETECTOR = ->(child) { child.type == :haml_comment }
9
12
 
10
13
  def visit_haml_comment(node)
@@ -16,13 +19,29 @@ module HamlLint
16
19
  config['max_consecutive'] + 1,
17
20
  ) do |group|
18
21
  group.each { |group_node| reported_nodes << group_node }
22
+ corrected = correct_group(group)
19
23
  record_lint(group.first,
20
- "#{group.count} consecutive comments can be merged into one")
24
+ "#{group.count} consecutive comments can be merged into one",
25
+ corrected: corrected)
21
26
  end
22
27
  end
23
28
 
24
29
  private
25
30
 
31
+ # @return [Boolean]
32
+ def correct_group(group)
33
+ first_index = group.first.line - 1
34
+ continuation_indent = "#{autocorrected_lines[first_index][/\A\s*/]} "
35
+
36
+ changed = false
37
+ group[1..].each do |group_node|
38
+ index = group_node.line - 1
39
+ line = autocorrected_lines[index]
40
+ changed = true if correct_line(index, continuation_indent + line.sub(/\A\s*-#\s?/, ''))
41
+ end
42
+ changed
43
+ end
44
+
26
45
  def possible_group(node)
27
46
  node.subsequents.unshift(node)
28
47
  end