haml_lint 0.45.0 → 0.47.0

Sign up to get free protection for your applications and to get access to all the features.
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