haml_lint 0.45.0 → 0.47.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/bin/haml-lint +1 -1
  3. data/config/default.yml +6 -28
  4. data/config/forced_rubocop_config.yml +171 -0
  5. data/lib/haml_lint/adapter/haml_4.rb +18 -0
  6. data/lib/haml_lint/adapter/haml_5.rb +11 -0
  7. data/lib/haml_lint/adapter/haml_6.rb +11 -0
  8. data/lib/haml_lint/cli.rb +8 -3
  9. data/lib/haml_lint/configuration_loader.rb +9 -10
  10. data/lib/haml_lint/document.rb +89 -8
  11. data/lib/haml_lint/exceptions.rb +6 -0
  12. data/lib/haml_lint/extensions/haml_util_unescape_interpolation_tracking.rb +35 -0
  13. data/lib/haml_lint/file_finder.rb +2 -2
  14. data/lib/haml_lint/lint.rb +10 -1
  15. data/lib/haml_lint/linter/final_newline.rb +4 -3
  16. data/lib/haml_lint/linter/implicit_div.rb +1 -1
  17. data/lib/haml_lint/linter/indentation.rb +3 -3
  18. data/lib/haml_lint/linter/no_placeholders.rb +18 -0
  19. data/lib/haml_lint/linter/rubocop.rb +353 -59
  20. data/lib/haml_lint/linter/space_before_script.rb +8 -10
  21. data/lib/haml_lint/linter/unnecessary_string_output.rb +1 -1
  22. data/lib/haml_lint/linter/view_length.rb +1 -1
  23. data/lib/haml_lint/linter.rb +60 -10
  24. data/lib/haml_lint/linter_registry.rb +3 -5
  25. data/lib/haml_lint/logger.rb +2 -2
  26. data/lib/haml_lint/options.rb +26 -2
  27. data/lib/haml_lint/rake_task.rb +2 -2
  28. data/lib/haml_lint/reporter/hash_reporter.rb +1 -3
  29. data/lib/haml_lint/reporter/offense_count_reporter.rb +1 -1
  30. data/lib/haml_lint/reporter/utils.rb +33 -4
  31. data/lib/haml_lint/ruby_extraction/ad_hoc_chunk.rb +24 -0
  32. data/lib/haml_lint/ruby_extraction/base_chunk.rb +114 -0
  33. data/lib/haml_lint/ruby_extraction/chunk_extractor.rb +509 -0
  34. data/lib/haml_lint/ruby_extraction/coordinator.rb +181 -0
  35. data/lib/haml_lint/ruby_extraction/haml_comment_chunk.rb +54 -0
  36. data/lib/haml_lint/ruby_extraction/implicit_end_chunk.rb +17 -0
  37. data/lib/haml_lint/ruby_extraction/interpolation_chunk.rb +26 -0
  38. data/lib/haml_lint/ruby_extraction/non_ruby_filter_chunk.rb +32 -0
  39. data/lib/haml_lint/ruby_extraction/placeholder_marker_chunk.rb +40 -0
  40. data/lib/haml_lint/ruby_extraction/ruby_filter_chunk.rb +33 -0
  41. data/lib/haml_lint/ruby_extraction/ruby_source.rb +5 -0
  42. data/lib/haml_lint/ruby_extraction/script_chunk.rb +132 -0
  43. data/lib/haml_lint/ruby_extraction/tag_attributes_chunk.rb +39 -0
  44. data/lib/haml_lint/ruby_extraction/tag_script_chunk.rb +30 -0
  45. data/lib/haml_lint/ruby_extractor.rb +11 -10
  46. data/lib/haml_lint/runner.rb +35 -3
  47. data/lib/haml_lint/spec/matchers/report_lint.rb +22 -7
  48. data/lib/haml_lint/spec/normalize_indent.rb +2 -2
  49. data/lib/haml_lint/spec/shared_linter_context.rb +9 -1
  50. data/lib/haml_lint/spec/shared_rubocop_autocorrect_context.rb +158 -0
  51. data/lib/haml_lint/spec.rb +1 -0
  52. data/lib/haml_lint/tree/filter_node.rb +10 -0
  53. data/lib/haml_lint/tree/node.rb +13 -4
  54. data/lib/haml_lint/tree/tag_node.rb +5 -9
  55. data/lib/haml_lint/utils.rb +130 -5
  56. data/lib/haml_lint/version.rb +1 -1
  57. data/lib/haml_lint/version_comparer.rb +25 -0
  58. data/lib/haml_lint.rb +12 -0
  59. metadata +25 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d47102747f37b5169cf47af34064ea1346df32e5e530cc1d1f17c293de9a34e
4
- data.tar.gz: aebecb2a1f37c6c0b20c67ec176c488bce59f667340d4f5a6cbe43736d7532bc
3
+ metadata.gz: e7a818da3ed1d58db0ad35b864c6c90d8036173109f6609da452da8b496dd1d5
4
+ data.tar.gz: ca045650d3373d2fa48d840c684c14b3d46a497f39e91eb9058b1827f3bd74b7
5
5
  SHA512:
6
- metadata.gz: e428d39889b5ab2857560151bea5e233d49ce80bac3fb1dc454e7f042567de7ec05471488c60d774e9506ce72399ac2e4d821d3e106126539740eafa78f2ab0f
7
- data.tar.gz: efea4f7dbd70a5669f56f57871ffa29bdac38120e90943c3393ab941421ffd4cbfaf941226d71d707bbbda4800966d317350cb24f9cc15528a6418c42d4bd702
6
+ metadata.gz: 0e2d83720d795e4589d534087c4db2be681eeae95cecda6432917345dea5e4c59e8ba7d65633fb2d4cd0ea86eafc0ea89b8fc51f994cc0b7973a50b0fa0a0abd
7
+ data.tar.gz: 11fc0ea04b7dae506bb0e264d204bdcdcaa96a65230ade8416ae56094a46f95deb3a1006200919804769dcc1c7adf7ab407bbfdf863355248eea9b0e7858308a
data/bin/haml-lint CHANGED
@@ -4,5 +4,5 @@
4
4
  require 'haml_lint'
5
5
  require 'haml_lint/cli'
6
6
 
7
- logger = HamlLint::Logger.new(STDOUT)
7
+ logger = HamlLint::Logger.new($stdout)
8
8
  exit HamlLint::CLI.new(logger).run(ARGV)
data/config/default.yml CHANGED
@@ -76,6 +76,9 @@ linters:
76
76
  MultilineScript:
77
77
  enabled: true
78
78
 
79
+ NoPlaceholders:
80
+ enabled: false
81
+
79
82
  ObjectReferenceAttributes:
80
83
  enabled: true
81
84
 
@@ -85,34 +88,9 @@ linters:
85
88
 
86
89
  RuboCop:
87
90
  enabled: true
88
- # These cops are incredibly noisy when it comes to HAML templates, so we
89
- # ignore them.
90
- ignored_cops:
91
- - Lint/BlockAlignment
92
- - Lint/EndAlignment
93
- - Lint/Void
94
- - Layout/AlignHash # renamed to Layout/HashAlignment in rubocop 0.77
95
- - Layout/AlignParameters # renamed to Layout/ParameterAlignment in rubocop 0.77
96
- - Layout/ArgumentAlignment
97
- - Layout/CaseIndentation
98
- - Layout/ElseAlignment
99
- - Layout/EndOfLine
100
- - Layout/FirstHashElementIndentation
101
- - Layout/HashAlignment
102
- - Layout/IndentationWidth
103
- - Layout/LineLength # renamed from Metrics/LineLength in rubocop 0.79.0
104
- - Layout/ParameterAlignment
105
- - Layout/TrailingBlankLines # renamed to Layout/TrailingEmptyLines in rubocop 0.77
106
- - Layout/TrailingEmptyLines
107
- - Layout/TrailingWhitespace
108
- - Metrics/BlockLength
109
- - Metrics/BlockNesting
110
- - Metrics/LineLength
111
- - Naming/FileName
112
- - Style/FrozenStringLiteralComment
113
- - Style/IfUnlessModifier
114
- - Style/Next
115
- - Style/WhileUntilModifier
91
+ # Users can ignore cops using this configuration instead of editing their rubocop configuration.
92
+ # Mostly there for backward compatibility.
93
+ ignored_cops: []
116
94
 
117
95
  RubyComments:
118
96
  enabled: true
@@ -0,0 +1,171 @@
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
5
+ #
6
+ # So these configuration should not be overwritable by users.
7
+
8
+ Layout/ArgumentAlignment:
9
+ # The alternative, with_fixed_indentation, breaks because we sometimes remove indentation when
10
+ # dealing with multi-line scripts. (Because a line starting with "=" adds a "HL.out = " to the
11
+ # intermediary Ruby source, which requires indentation, and the removal of the indentation)
12
+ EnforcedStyle: with_first_argument
13
+
14
+ Layout/ArrayAlignment:
15
+ # The alternative, with_fixed_indentation, breaks because we sometimes remove indentation when
16
+ # dealing with multi-line scripts. (Because a line starting with "=" adds a "HL.out = " to the
17
+ # intermediary Ruby source, which requires indentation, and the removal of the indentation)
18
+ EnforcedStyle: with_first_element
19
+
20
+ # In Haml, there are edge cases when `when` is indented, such as this one:
21
+ # - case 1
22
+ # - when 1
23
+ # - when 2
24
+ # foo
25
+ # This generates 2 `end` when building Ruby.
26
+ # So it's safer to make the `when` be not indented, to avoid auto-correct chains that could turn
27
+ # a valid `if` into an invalid `case` such as above.
28
+ Layout/CaseIndentation:
29
+ Enabled: true
30
+ EnforcedStyle: end
31
+ IndentOneStep: false # Need to force the `false`
32
+
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.
35
+ Layout/ElseAlignment:
36
+ Enabled: true
37
+
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.
40
+ Layout/EndAlignment:
41
+ EnforcedStyleAlignWith: start_of_line
42
+ Enabled: true
43
+
44
+ # We generate the ruby content, this is basically useless and should be a lint in HAML-Lint
45
+ Layout/EndOfLine:
46
+ Enabled: false
47
+
48
+ # Turning this cop on can turn
49
+ # = content_tag(:span) do
50
+ # - foo
51
+ # - bar
52
+ #
53
+ # Into
54
+ # - HL.out =
55
+ # - content_tag(:span) do
56
+ # - foo
57
+ # - bar
58
+ #
59
+ # Which is wrong... It would take too much analysis to detect and fix that situation.
60
+ Layout/MultilineAssignmentLayout:
61
+ Enabled: false
62
+
63
+ Layout/ParameterAlignment:
64
+ # The alternative, with_fixed_indentation, breaks because we sometimes remove indentation when
65
+ # dealing with multi-line scripts. (Because a line starting with "=" adds a "HL.out = " to the
66
+ # intermediary Ruby source, which requires indentation, and the removal of the indentation)
67
+ EnforcedStyle: with_first_parameter
68
+
69
+ # HamlLint generate lots of extra code which would make blocks much longer
70
+ Metrics/BlockLength:
71
+ Enabled: false
72
+
73
+ # The nesting may be due to the html's nesting nature... These lints are probably not helpful
74
+ Metrics/BlockNesting:
75
+ Enabled: false
76
+
77
+ # The file names are generated by HamlLint, so any related lint would be unfixable by the user
78
+ Naming/FileName:
79
+ Enabled: false
80
+
81
+ # HAML doesn't properly support multiline blocks using { }, only using do/end.
82
+ # If you don't consider the { } block for indentation, things "works", but the indentation is misleading.
83
+ # For example, this works:
84
+ # - a = lambda {
85
+ # - if abc
86
+ # - something
87
+ # - }
88
+ # But if you indented the 2 lines within { }, then HAML would add an extra `end` and the generated
89
+ # ruby would be invalid.
90
+ Style/BlockDelimiters:
91
+ # So we need this cop to cleanup those cases and turn them to `end`.
92
+ Enabled: true
93
+ EnforcedStyle: line_count_based
94
+ # We don't allow the default "Can be anything" exception for lambda/proc
95
+ <%= rubocop_version < '1.33' ? 'IgnoredMethods' : 'AllowedMethods' %>: []
96
+
97
+ # We don't support correcting HAML comments
98
+ Style/CommentAnnotation:
99
+ AutoCorrect: false
100
+
101
+ # If this was enabled, the equal sign would bubble up in a if like this:
102
+ # - if a
103
+ # = abc
104
+ # - else
105
+ # = bcd
106
+ # Into:
107
+ # = if a
108
+ # - abc
109
+ # - else
110
+ # - bcd
111
+ # Feels like this might be annoying or less visibly intuitive.
112
+ Style/ConditionalAssignment:
113
+ Enabled: false
114
+
115
+ # If this gets added, it wont do anything anyways.
116
+ Style/FrozenStringLiteralComment:
117
+ Enabled: false
118
+
119
+ # Looking at the changelog, this cop has quite a few bugfixes over time.
120
+ # It still has problematic behaviors for us, such as breaking a line into multiple
121
+ # ones with high indentation, which doesn't work for haml
122
+ Style/IfUnlessModifier:
123
+ AutoCorrect: false
124
+
125
+ # In some case, this cop can cause a change in the spacing.
126
+ # In HAML 5.2, going from (absurd example for clarity):
127
+ # = 'abc' rescue nil
128
+ # = 'def'
129
+ # to:
130
+ # = begin
131
+ # - 'abc'
132
+ # - rescue StandardError
133
+ # - nil
134
+ # = 'def'
135
+ # Will remove the only whitespace (a \n) that is between the abc and the def.
136
+ # This could affect spacing, ex: seeing "abc def" become "abcdef" when rendering
137
+ # after running this auto-correct
138
+ Style/RescueModifier:
139
+ AutoCorrect: false
140
+
141
+ # Cops that remove commas can be a problem when lines are split on multiple ones.
142
+ # If we have a big array on more than one line, the removal of the comma generates
143
+ # invalid HAML
144
+ Style/SymbolArray:
145
+ Enabled: false
146
+
147
+ # This can easily change the order of the markers in the document, which result in un-transferable corrections
148
+ Style/UnlessElse:
149
+ AutoCorrect: false
150
+
151
+ # If an array of strings was on multiple lines, this cop will make a %w(...) on multiple lines.
152
+ # Without the comma at the end of the first line, there the resulting HAML will be invalid, since the only
153
+ # case where a script can change line is after a comma.
154
+ Style/WordArray:
155
+ AutoCorrect: false
156
+
157
+ # Not RuboCop's job
158
+ Layout/TrailingEmptyLines:
159
+ Enabled: false
160
+
161
+ <% if rubocop_version < '1.8.1' %>
162
+ # There were a few bugs with this cop that got fixed in this version.
163
+ # Before, those bugs would generate invalid Ruby code and that would make it look like HAML-lint is
164
+ # responsible, at least from the user's point of view.
165
+ Style/StringConcatenation:
166
+ Enabled: false
167
+ <% end %>
168
+
169
+ # There is already a linter dedicated to this in haml-lint
170
+ Layout/LineLength:
171
+ Enabled: false
@@ -18,9 +18,21 @@ module HamlLint
18
18
  # @param source [String] Haml code to parse
19
19
  # @param options [Haml::Options]
20
20
  def initialize(source, options = Haml::Options.new)
21
+ @source = source
21
22
  @parser = Haml::Parser.new(source, options)
22
23
  end
23
24
 
25
+ def precompile
26
+ # Haml uses the filters as part of precompilation... we don't care about those,
27
+ # but without this tweak, it would fail on filters that are not loaded.
28
+ real_defined = Haml::Filters.defined
29
+ Haml::Filters.instance_variable_set(:@defined, Hash.new { real_defined['plain'] })
30
+
31
+ ::Haml::Engine.new(source).precompiled
32
+ ensure
33
+ Haml::Filters.instance_variable_set(:@defined, real_defined)
34
+ end
35
+
24
36
  # @!method
25
37
  # Parses the source code into an abstract syntax tree
26
38
  #
@@ -39,6 +51,12 @@ module HamlLint
39
51
  # @api private
40
52
  # @return [Haml::Parser] the Haml 4 parser
41
53
  attr_reader :parser
54
+
55
+ # The Haml code to parse
56
+ #
57
+ # @api private
58
+ # @return [String] Haml code to parse
59
+ attr_reader :source
42
60
  end
43
61
  end
44
62
  end
@@ -30,6 +30,17 @@ module HamlLint
30
30
  parser.call(source)
31
31
  end
32
32
 
33
+ def precompile
34
+ # Haml uses the filters as part of precompilation... we don't care about those,
35
+ # but without this tweak, it would fail on filters that are not loaded.
36
+ real_defined = Haml::Filters.defined
37
+ Haml::Filters.instance_variable_set(:@defined, Hash.new { real_defined['plain'] })
38
+
39
+ ::Haml::Engine.new(source).precompiled
40
+ ensure
41
+ Haml::Filters.instance_variable_set(:@defined, real_defined)
42
+ end
43
+
33
44
  private
34
45
 
35
46
  # The Haml parser to adapt for HamlLint
@@ -30,6 +30,17 @@ module HamlLint
30
30
  parser.call(source)
31
31
  end
32
32
 
33
+ def precompile
34
+ # Haml uses the filters as part of precompilation... we don't care about those,
35
+ # but without this tweak, it would fail on filters that are not loaded.
36
+ real_defined = Haml::Filters.registered
37
+ Haml::Filters.instance_variable_set(:@registered, Hash.new { real_defined['plain'] })
38
+
39
+ ::Haml::Engine.new.call(source)
40
+ ensure
41
+ Haml::Filters.instance_variable_set(:@registered, real_defined)
42
+ end
43
+
33
44
  private
34
45
 
35
46
  # The Haml parser to adapt for HamlLint
data/lib/haml_lint/cli.rb CHANGED
@@ -7,7 +7,7 @@ require 'sysexits'
7
7
 
8
8
  module HamlLint
9
9
  # Command line application interface.
10
- class CLI # rubocop:disable Metrics/ClassLength
10
+ class CLI
11
11
  # Create a CLI that outputs to the specified logger.
12
12
  #
13
13
  # @param logger [HamlLint::Logger]
@@ -34,9 +34,14 @@ module HamlLint
34
34
  # Given the provided options, execute the appropriate command.
35
35
  #
36
36
  # @return [Integer] exit status code
37
- def act_on_options(options)
37
+ def act_on_options(options) # rubocop:disable Metrics
38
38
  configure_logger(options)
39
-
39
+ if options[:debug]
40
+ ENV['HAML_LINT_DEBUG'] = 'true'
41
+ end
42
+ if options[:internal_debug]
43
+ ENV['HAML_LINT_INTERNAL_DEBUG'] = 'true'
44
+ end
40
45
  if options[:help]
41
46
  print_help(options)
42
47
  Sysexits::EX_OK
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'pathname'
4
4
  require 'yaml'
5
+ require 'erb'
5
6
 
6
7
  module HamlLint
7
8
  # Manages configuration file loading.
@@ -31,7 +32,7 @@ module HamlLint
31
32
  def default_path_to_config
32
33
  directory = File.expand_path(Dir.pwd)
33
34
  config_file = possible_config_files(directory).find(&:file?)
34
- config_file ? config_file.to_path : nil
35
+ config_file&.to_path
35
36
  end
36
37
 
37
38
  # Loads the built-in default configuration.
@@ -48,7 +49,7 @@ module HamlLint
48
49
  # @option context :exclude_files [Array<String>] files that should not
49
50
  # be loaded even if they're requested via inherits_from
50
51
  # @return [HamlLint::Configuration]
51
- def load_file(file, context = {})
52
+ def load_file(file, context = {}) # rubocop:disable Metrics
52
53
  context[:loaded_files] ||= []
53
54
  context[:loaded_files].map! { |config_file| File.expand_path(config_file) }
54
55
  context[:exclude_files] ||= []
@@ -85,13 +86,11 @@ module HamlLint
85
86
  #
86
87
  # @param file [String]
87
88
  # @return [HamlLint::Configuration]
88
- def load_from_file(file)
89
- hash =
90
- if yaml = YAML.load_file(file)
91
- yaml.to_hash
92
- else
93
- {}
94
- end
89
+ def load_from_file(file) # rubocop:disable Metrics
90
+ content = File.read(file)
91
+
92
+ processed_content = HamlLint::Utils.process_erb(content)
93
+ hash = (YAML.safe_load(processed_content) || {}).to_hash
95
94
 
96
95
  if hash.key?('inherit_from')
97
96
  hash['inherits_from'] ||= []
@@ -157,7 +156,7 @@ module HamlLint
157
156
  #
158
157
  # @param gem_name [String] name of the gem
159
158
  # @param relative_config_path [String] path of the file to resolve, relative to the gem root
160
- # @return [Stringg]
159
+ # @return [String]
161
160
  def gem_config_path(gem_name, relative_config_path)
162
161
  if defined?(Bundler)
163
162
  gem = Bundler.load.specs[gem_name].first
@@ -23,6 +23,14 @@ module HamlLint
23
23
  # @return [Array<String>] original source code as an array of lines
24
24
  attr_reader :source_lines
25
25
 
26
+ # @return [Boolean] true if the source was changed (by autocorrect)
27
+ attr_reader :source_was_changed
28
+
29
+ # @return [String] the indentation used in the file
30
+ attr_reader :indentation
31
+
32
+ attr_reader :unescape_interpolation_to_original_cache
33
+
26
34
  # Parses the specified Haml code into a {Document}.
27
35
  #
28
36
  # @param source [String] Haml code to parse
@@ -32,22 +40,80 @@ module HamlLint
32
40
  def initialize(source, options)
33
41
  @config = options[:config]
34
42
  @file = options.fetch(:file, STRING_SOURCE)
35
-
43
+ @source_was_changed = false
36
44
  process_source(source)
37
45
  end
38
46
 
47
+ # Returns the last non empty line of the document or 1 if all lines are empty
48
+ #
49
+ # @return [Integer] last non empty line of the document or 1 if all lines are empty
50
+ def last_non_empty_line
51
+ index = source_lines.rindex { |l| !l.empty? }
52
+ (index || 0) + 1
53
+ end
54
+
55
+ # Reparses the new source and remember that the document was changed
56
+ # Used when auto-correct does changes to the file. If the source hasn't changed,
57
+ # then the document will not be marked as changed.
58
+ #
59
+ # If the new_source fails to parse, automatically reparses the previous source
60
+ # to bring the document back to how it should be before re-raising the parse exception
61
+ #
62
+ # @param source [String] Haml code to parse
63
+ def change_source(new_source)
64
+ return if new_source == @source
65
+ check_new_source_compatible(new_source)
66
+
67
+ old_source = @source
68
+ begin
69
+ process_source(new_source)
70
+ @source_was_changed = true
71
+ rescue HamlLint::Exceptions::ParseError
72
+ # Reprocess the previous_source so that other linters can work on this document
73
+ # object from a clean slate
74
+ process_source(old_source)
75
+ raise
76
+ end
77
+ nil
78
+ end
79
+
80
+ def write_to_disk!
81
+ return unless @source_was_changed
82
+ if file == STRING_SOURCE
83
+ raise HamlLint::Exceptions::InvalidFilePath, 'Cannot write without :file option'
84
+ end
85
+ File.write(file, unstrip_frontmatter(source))
86
+ @source_was_changed = false
87
+ end
88
+
39
89
  private
40
90
 
41
91
  # @param source [String] Haml code to parse
42
92
  # @raise [HamlLint::Exceptions::ParseError] if there was a problem parsing
43
- def process_source(source)
93
+ def process_source(source) # rubocop:disable Metrics/MethodLength
44
94
  @source = process_encoding(source)
45
95
  @source = strip_frontmatter(source)
46
- @source_lines = @source.split(/\r\n|\r|\n/)
47
-
48
- @tree = process_tree(HamlLint::Adapter.detect_class.new(@source).parse)
96
+ # the -1 is to keep the empty strings at the end of the array when the source
97
+ # ended with multiple new-lines
98
+ @source_lines = @source.split(/\r\n|\r|\n/, -1)
99
+ adapter = HamlLint::Adapter.detect_class.new(@source)
100
+ parsed_tree = adapter.parse
101
+ @indentation = adapter.send(:parser).instance_variable_get(:@indentation)
102
+ @tree = process_tree(parsed_tree)
103
+ @unescape_interpolation_to_original_cache =
104
+ Haml::Util.unescape_interpolation_to_original_cache_take_and_wipe
49
105
  rescue Haml::Error => e
50
- error = HamlLint::Exceptions::ParseError.new(e.message, e.line)
106
+ location = if e.line
107
+ "#{@file}:#{e.line}"
108
+ else
109
+ @file
110
+ end
111
+ msg = if ENV['HAML_LINT_DEBUG'] == 'true'
112
+ "#{location} (DEBUG: source follows) - #{e.message}\n#{source}\n------"
113
+ else
114
+ "#{location} - #{e.message}"
115
+ end
116
+ error = HamlLint::Exceptions::ParseError.new(msg, e.line)
51
117
  raise error
52
118
  end
53
119
 
@@ -114,11 +180,26 @@ module HamlLint
114
180
  (---|\.\.\.)\s*$\n?/mx
115
181
 
116
182
  if config['skip_frontmatter'] && match = source.match(frontmatter)
117
- newlines = match[0].count("\n")
118
- source.sub!(frontmatter, "\n" * newlines)
183
+ @stripped_frontmatter = match[0]
184
+ @nb_newlines_for_frontmatter = match[0].count("\n")
185
+ source.sub!(frontmatter, "\n" * @nb_newlines_for_frontmatter)
119
186
  end
120
187
 
121
188
  source
122
189
  end
190
+
191
+ def check_new_source_compatible(new_source)
192
+ if @stripped_frontmatter && !new_source.start_with?("\n" * @nb_newlines_for_frontmatter)
193
+ raise HamlLint::Exceptions::IncompatibleNewSource,
194
+ "Internal error: new_source doesn't start with enough newlines for the Front Matter that was stripped"
195
+ end
196
+ end
197
+
198
+ def unstrip_frontmatter(source)
199
+ return source unless @stripped_frontmatter
200
+ check_new_source_compatible(source)
201
+
202
+ source.sub("\n" * @nb_newlines_for_frontmatter, @stripped_frontmatter)
203
+ end
123
204
  end
124
205
  end
@@ -5,6 +5,12 @@ module HamlLint::Exceptions
5
5
  # Raised when a {Configuration} could not be loaded from a file.
6
6
  class ConfigurationError < StandardError; end
7
7
 
8
+ # Raised trying to change source with incompatible one (ex: due to frontmatter)
9
+ class IncompatibleNewSource < StandardError; end
10
+
11
+ # Raised when linter's autocorrection cause an infinite loop
12
+ class InfiniteLoopError < StandardError; end
13
+
8
14
  # Raised when invalid/incompatible command line options are provided.
9
15
  class InvalidCLIOption < StandardError; end
10
16
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Haml does heavy transformations to strings that contain interpolation without a way
4
+ # of perfectly inverting that transformation.
5
+ #
6
+ # We need this monkey patch to have a way of recovering the original strings as they
7
+ # are in the haml files, so that we can use them and then autocorrect them.
8
+ #
9
+ # The HamlLint::Document carries over a hash of interpolation to original string. The
10
+ # below patches are there to extract said information from Haml's parsing.
11
+ module Haml::Util
12
+ # The cache for the current Thread (technically Fiber)
13
+ def self.unescape_interpolation_to_original_cache
14
+ Thread.current[:haml_lint_unescape_interpolation_to_original_cache] ||= {}
15
+ end
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
19
+ def self.unescape_interpolation_to_original_cache_take_and_wipe
20
+ value = unescape_interpolation_to_original_cache.dup
21
+ unescape_interpolation_to_original_cache.clear
22
+ value
23
+ end
24
+
25
+ # Overriding the unescape_interpolation method to store the return and original string
26
+ # in the cache.
27
+ def unescape_interpolation_with_original_tracking(str, escape_html = nil)
28
+ value = unescape_interpolation_without_original_tracking(str, escape_html)
29
+ Haml::Util.unescape_interpolation_to_original_cache[value] = str
30
+ value
31
+ end
32
+
33
+ alias unescape_interpolation_without_original_tracking unescape_interpolation
34
+ alias unescape_interpolation unescape_interpolation_with_original_tracking
35
+ end
@@ -40,7 +40,7 @@ module HamlLint
40
40
  #
41
41
  # @param patterns [Array<String>]
42
42
  # @return [Array<String>]
43
- def extract_files_from(patterns) # rubocop:disable Metrics/MethodLength
43
+ def extract_files_from(patterns) # rubocop:disable Metrics
44
44
  files = []
45
45
 
46
46
  patterns.each do |pattern|
@@ -74,7 +74,7 @@ module HamlLint
74
74
  # @param path [String]
75
75
  # @return [String]
76
76
  def normalize_path(path)
77
- path.start_with?(".#{File::SEPARATOR}") ? path[2..-1] : path
77
+ path.start_with?(".#{File::SEPARATOR}") ? path[2..] : path
78
78
  end
79
79
 
80
80
  # Whether the given file should be treated as a Haml file.
@@ -3,6 +3,9 @@
3
3
  module HamlLint
4
4
  # Contains information about a problem or issue with a HAML document.
5
5
  class Lint
6
+ # @return [Boolean] If the error was corrected by auto-correct
7
+ attr_reader :corrected
8
+
6
9
  # @return [String] file path to which the lint applies
7
10
  attr_reader :filename
8
11
 
@@ -25,12 +28,13 @@ module HamlLint
25
28
  # @param line [Fixnum]
26
29
  # @param message [String]
27
30
  # @param severity [Symbol]
28
- def initialize(linter, filename, line, message, severity = :warning)
31
+ def initialize(linter, filename, line, message, severity = :warning, corrected: false) # rubocop:disable Metrics/ParameterLists
29
32
  @linter = linter
30
33
  @filename = filename
31
34
  @line = line || 0
32
35
  @message = message
33
36
  @severity = Severity.new(severity)
37
+ @corrected = corrected
34
38
  end
35
39
 
36
40
  # Return whether this lint has a severity of error.
@@ -39,5 +43,10 @@ module HamlLint
39
43
  def error?
40
44
  @severity.error?
41
45
  end
46
+
47
+ def inspect
48
+ "#{self.class.name}(corrected=#{corrected}, filename=#{filename}, line=#{line}, " \
49
+ "linter=#{linter.class.name}, message=#{message}, severity=#{severity})"
50
+ end
42
51
  end
43
52
  end
@@ -7,18 +7,19 @@ module HamlLint
7
7
 
8
8
  def visit_root(root)
9
9
  return if document.source.empty?
10
+ line_number = document.last_non_empty_line
10
11
 
11
- node = root.node_for_line(document.source_lines.count)
12
+ node = root.node_for_line(line_number)
12
13
  return if node.disabled?(self)
13
14
 
14
15
  ends_with_newline = document.source.end_with?("\n")
15
16
 
16
17
  if config['present']
17
18
  unless ends_with_newline
18
- record_lint(node, 'Files should end with a trailing newline')
19
+ record_lint(line_number, 'Files should end with a trailing newline')
19
20
  end
20
21
  elsif ends_with_newline
21
- record_lint(node, 'Files should not end with a trailing newline')
22
+ record_lint(line_number, 'Files should not end with a trailing newline')
22
23
  end
23
24
  end
24
25
  end
@@ -11,7 +11,7 @@ module HamlLint
11
11
 
12
12
  return unless node.static_classes.any? || node.static_ids.any?
13
13
 
14
- tag = node.source_code[/\s*([^\s={\(\[]+)/, 1]
14
+ tag = node.source_code[/\s*([^\s={(\[]+)/, 1]
15
15
  return unless tag.start_with?('%div')
16
16
 
17
17
  record_lint(node,
@@ -7,8 +7,8 @@ module HamlLint
7
7
 
8
8
  # Allowed leading indentation for each character type.
9
9
  INDENT_REGEX = {
10
- space: /^[ ]*(?!\t)/,
11
- tab: /^\t*(?![ ])/,
10
+ space: /^ *(?!\t)/,
11
+ tab: /^\t*(?! )/,
12
12
  }.freeze
13
13
 
14
14
  LEADING_SPACES_REGEX = /^( +)(?! )/.freeze
@@ -45,7 +45,7 @@ module HamlLint
45
45
  root.children.each do |top_node|
46
46
  # once we've found one line with leading space, there's no need to check any more lines
47
47
  # `haml` will check indenting_at_start, deeper_indenting, inconsistent_indentation
48
- break if top_node.children.find do |node|
48
+ break if top_node.children.find do |node| # rubocop:disable Lint/UnreachableLoop
49
49
  line = node.source_code
50
50
  leading_space = LEADING_SPACES_REGEX.match(line)
51
51