haml_lint 0.73.0 → 0.75.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.
- checksums.yaml +4 -4
- data/config/default.yml +10 -5
- data/config/forced_rubocop_config.yml +13 -13
- data/lib/haml_lint/cli.rb +5 -1
- data/lib/haml_lint/document.rb +3 -8
- data/lib/haml_lint/exceptions.rb +2 -2
- data/lib/haml_lint/extensions/haml_util_unescape_interpolation_tracking.rb +2 -2
- data/lib/haml_lint/haml_visitor.rb +1 -1
- data/lib/haml_lint/lint.rb +1 -1
- data/lib/haml_lint/linter/class_attribute_with_static_value.rb +51 -11
- data/lib/haml_lint/linter/classes_before_ids.rb +16 -1
- data/lib/haml_lint/linter/consecutive_comments.rb +20 -1
- data/lib/haml_lint/linter/empty_object_reference.rb +16 -1
- data/lib/haml_lint/linter/empty_script.rb +31 -1
- data/lib/haml_lint/linter/final_newline.rb +31 -7
- data/lib/haml_lint/linter/implicit_div.rb +14 -1
- data/lib/haml_lint/linter/leading_comment_space.rb +13 -1
- data/lib/haml_lint/linter/multiline_script.rb +65 -5
- data/lib/haml_lint/linter/rubocop.rb +10 -22
- data/lib/haml_lint/linter/ruby_comments.rb +14 -4
- data/lib/haml_lint/linter/space_before_script.rb +37 -4
- data/lib/haml_lint/linter/space_inside_hash_attributes.rb +41 -3
- data/lib/haml_lint/linter/tag_name.rb +14 -1
- data/lib/haml_lint/linter/trailing_empty_lines.rb +13 -1
- data/lib/haml_lint/linter/trailing_whitespace.rb +15 -2
- data/lib/haml_lint/linter/unescaped_html.rb +27 -0
- data/lib/haml_lint/linter/unnecessary_interpolation.rb +30 -3
- data/lib/haml_lint/linter/unnecessary_string_output.rb +35 -3
- data/lib/haml_lint/linter.rb +112 -1
- data/lib/haml_lint/node_transformer.rb +2 -2
- data/lib/haml_lint/reporter/disabled_config_reporter.rb +26 -5
- data/lib/haml_lint/reporter/github_reporter.rb +26 -8
- data/lib/haml_lint/ruby_extraction/base_chunk.rb +3 -3
- data/lib/haml_lint/ruby_extraction/chunk_extractor.rb +37 -13
- data/lib/haml_lint/ruby_extraction/coordinator.rb +2 -2
- data/lib/haml_lint/ruby_extraction/implicit_end_chunk.rb +1 -1
- data/lib/haml_lint/ruby_extraction/non_ruby_filter_chunk.rb +1 -1
- data/lib/haml_lint/ruby_extraction/placeholder_marker_chunk.rb +1 -1
- data/lib/haml_lint/ruby_extraction/script_chunk.rb +1 -1
- data/lib/haml_lint/runner.rb +31 -3
- data/lib/haml_lint/source.rb +2 -6
- data/lib/haml_lint/spec/matchers/report_lint.rb +2 -2
- data/lib/haml_lint/tree/comment_node.rb +1 -1
- data/lib/haml_lint/tree/doctype_node.rb +1 -1
- data/lib/haml_lint/tree/filter_node.rb +14 -1
- data/lib/haml_lint/tree/haml_comment_node.rb +2 -2
- data/lib/haml_lint/tree/node.rb +3 -3
- data/lib/haml_lint/tree/root_node.rb +2 -2
- data/lib/haml_lint/tree/silent_script_node.rb +1 -1
- data/lib/haml_lint/tree/tag_node.rb +57 -25
- data/lib/haml_lint/utils.rb +1 -1
- data/lib/haml_lint/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8bb347cd92b5e23b37b8beb59d2c67f23eecd1e5778fdc45a7c4fbd73d14f854
|
|
4
|
+
data.tar.gz: 93836aaa95ab3bde6932969c81fb9e409c9806ab9057a1162580e1771f1edff7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5c5789d9ca6de3d2cae48ef91277b374ed84b477323389ccc7049d1d4e690ef1cb2252781074c902c7b82fa9dd332a36ace0d25652723841e170c89c9c805e15
|
|
7
|
+
data.tar.gz: 325df8db407db7567bf88db824dc2a071af3457c827b5268e96e6e2e17f0a63b6192974082ed8e4bab9ca0b645aacfbec981d7a25e6e9e7330d28f8c60df2123
|
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
|
|
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
|
|
54
|
-
width: 2
|
|
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
|
-
#
|
|
92
|
-
#
|
|
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:
|
|
@@ -118,6 +120,9 @@ linters:
|
|
|
118
120
|
TrailingWhitespace:
|
|
119
121
|
enabled: true
|
|
120
122
|
|
|
123
|
+
UnescapedHtml:
|
|
124
|
+
enabled: true
|
|
125
|
+
|
|
121
126
|
UnnecessaryInterpolation:
|
|
122
127
|
enabled: true
|
|
123
128
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# These are some configurations that are required for RuboCop because:
|
|
2
|
-
# *
|
|
3
|
-
#
|
|
4
|
-
# *
|
|
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
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
data/lib/haml_lint/cli.rb
CHANGED
|
@@ -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(
|
|
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
|
data/lib/haml_lint/document.rb
CHANGED
|
@@ -14,9 +14,6 @@ module HamlLint
|
|
|
14
14
|
# @return [String] Haml template file path
|
|
15
15
|
attr_reader :file
|
|
16
16
|
|
|
17
|
-
# @return [Boolean] true if source was read directly from `file` on-disk (rather than from stdin)
|
|
18
|
-
attr_reader :file_on_disk
|
|
19
|
-
|
|
20
17
|
# @return [Boolean] true if source changes (from autocorrect) should be written to stdout instead of disk
|
|
21
18
|
attr_reader :write_to_stdout
|
|
22
19
|
|
|
@@ -42,14 +39,12 @@ module HamlLint
|
|
|
42
39
|
# @param source [String] Haml code to parse
|
|
43
40
|
# @param options [Hash]
|
|
44
41
|
# @option options :file [String] file name of document that was parsed
|
|
45
|
-
# @option options :file_on_disk [Boolean] true if source was read straight from `file` on disk
|
|
46
42
|
# @option options :write_to_stdout [Boolean] true if source changes should be written to stdout
|
|
47
43
|
# @raise [Haml::Parser::Error] if there was a problem parsing the document
|
|
48
44
|
def initialize(source, options)
|
|
49
45
|
@config = options[:config]
|
|
50
46
|
@file = options.fetch(:file, STRING_SOURCE)
|
|
51
47
|
@write_to_stdout = options[:write_to_stdout]
|
|
52
|
-
@file_on_disk = options[:file_on_disk] && @file != STRING_SOURCE
|
|
53
48
|
@source_was_changed = false
|
|
54
49
|
process_source(source)
|
|
55
50
|
end
|
|
@@ -137,8 +132,8 @@ module HamlLint
|
|
|
137
132
|
# @param original_tree [Haml::Parser::ParseNode]
|
|
138
133
|
# @return [Haml::Tree::Node]
|
|
139
134
|
def process_tree(original_tree)
|
|
140
|
-
# Remove the trailing empty
|
|
141
|
-
# the end of the
|
|
135
|
+
# Remove the trailing empty Haml comment that the parser creates to signal
|
|
136
|
+
# the end of the Haml document
|
|
142
137
|
if Gem::Requirement.new('~> 4.0.0').satisfied_by?(Gem.loaded_specs['haml'].version)
|
|
143
138
|
original_tree.children.pop
|
|
144
139
|
end
|
|
@@ -147,7 +142,7 @@ module HamlLint
|
|
|
147
142
|
convert_tree(original_tree)
|
|
148
143
|
end
|
|
149
144
|
|
|
150
|
-
# Converts a
|
|
145
|
+
# Converts a Haml parse tree to a tree of {HamlLint::Tree::Node} objects.
|
|
151
146
|
#
|
|
152
147
|
# This provides a cleaner interface with which the linters can interact with
|
|
153
148
|
# the parse tree.
|
data/lib/haml_lint/exceptions.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Collection of exceptions that can be raised by the
|
|
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
|
|
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
|
|
18
|
-
# get a copy of this cache and clear up for the next
|
|
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
|
|
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
|
data/lib/haml_lint/lint.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module HamlLint
|
|
4
|
-
# Contains information about a problem or issue with a
|
|
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
|
|
@@ -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
25
|
VALID_CLASS_REGEX = /^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$/
|
|
24
26
|
|
|
25
27
|
def visit_tag(node)
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
+
value = static_class_attribute_value(pair)
|
|
50
|
+
return value if value
|
|
45
51
|
end
|
|
46
52
|
end
|
|
47
53
|
|
|
48
|
-
|
|
54
|
+
nil
|
|
49
55
|
end
|
|
50
56
|
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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,6 +5,8 @@ module HamlLint
|
|
|
5
5
|
class Linter::ClassesBeforeIds < Linter
|
|
6
6
|
include LinterRegistry
|
|
7
7
|
|
|
8
|
+
supports_autocorrect(true)
|
|
9
|
+
|
|
8
10
|
MSG = '%s should be listed before %s (%s should precede %s)'
|
|
9
11
|
|
|
10
12
|
def visit_tag(node)
|
|
@@ -17,14 +19,27 @@ module HamlLint
|
|
|
17
19
|
next unless next_val.start_with?(first) &&
|
|
18
20
|
current_val.start_with?(second)
|
|
19
21
|
|
|
22
|
+
corrected = correct_attribute_order(node, components)
|
|
20
23
|
failure_message = format(MSG, *(attribute_type_order + [next_val, current_val]))
|
|
21
|
-
record_lint(node, failure_message)
|
|
24
|
+
record_lint(node, failure_message, corrected: corrected)
|
|
22
25
|
break
|
|
23
26
|
end
|
|
24
27
|
end
|
|
25
28
|
|
|
26
29
|
private
|
|
27
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
|
+
|
|
28
43
|
def attribute_prefix_order
|
|
29
44
|
default = %w[. #]
|
|
30
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
|
|
@@ -5,11 +5,26 @@ module HamlLint
|
|
|
5
5
|
class Linter::EmptyObjectReference < Linter
|
|
6
6
|
include LinterRegistry
|
|
7
7
|
|
|
8
|
+
supports_autocorrect(true)
|
|
9
|
+
|
|
8
10
|
def visit_tag(node)
|
|
9
11
|
return unless node.object_reference? &&
|
|
10
12
|
node.object_reference_source.strip.empty?
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
corrected = correct_object_reference(node)
|
|
15
|
+
record_lint(node, 'Empty object reference should be removed', corrected: corrected)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
# @return [Boolean]
|
|
21
|
+
def correct_object_reference(node)
|
|
22
|
+
index = node.line - 1
|
|
23
|
+
line = autocorrected_lines[index]
|
|
24
|
+
# `static_attributes_source` is just the `.class`/`#id` part (e.g. `.foo`),
|
|
25
|
+
# so the optional `%tag` group captures an explicit tag name when present.
|
|
26
|
+
static = Regexp.escape(node.static_attributes_source)
|
|
27
|
+
correct_line(index, line.sub(/\A(\s*(?:%[-:\w]+)?#{static})\[\s*\]/, '\1'))
|
|
13
28
|
end
|
|
14
29
|
end
|
|
15
30
|
end
|
|
@@ -5,10 +5,40 @@ module HamlLint
|
|
|
5
5
|
class Linter::EmptyScript < Linter
|
|
6
6
|
include LinterRegistry
|
|
7
7
|
|
|
8
|
+
supports_autocorrect(true)
|
|
9
|
+
autocorrect_safe(false)
|
|
10
|
+
|
|
8
11
|
def visit_silent_script(node)
|
|
9
12
|
return unless /\A\s*\Z/.match?(node.script)
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
# Only a childless `-` can be deleted; a `-` with children is degenerate
|
|
15
|
+
# but is still reported.
|
|
16
|
+
deletable = node.children.empty?
|
|
17
|
+
deleted_lines << (node.line - 1) if autocorrect? && deletable
|
|
18
|
+
record_lint(node, 'Empty script should be removed',
|
|
19
|
+
corrected: autocorrect? && deletable)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def after_visit_root(node)
|
|
23
|
+
super
|
|
24
|
+
return if deleted_lines.empty?
|
|
25
|
+
|
|
26
|
+
kept = document.source_lines.reject.with_index do |_, index|
|
|
27
|
+
deleted_lines.include?(index)
|
|
28
|
+
end
|
|
29
|
+
apply_autocorrect(kept.join("\n"))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def reset_autocorrect_state
|
|
35
|
+
super
|
|
36
|
+
@deleted_lines = []
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [Array<Integer>]
|
|
40
|
+
def deleted_lines
|
|
41
|
+
@deleted_lines ||= []
|
|
12
42
|
end
|
|
13
43
|
end
|
|
14
44
|
end
|
|
@@ -5,6 +5,12 @@ module HamlLint
|
|
|
5
5
|
class Linter::FinalNewline < Linter
|
|
6
6
|
include LinterRegistry
|
|
7
7
|
|
|
8
|
+
supports_autocorrect(true)
|
|
9
|
+
|
|
10
|
+
# Run last during autocorrect, so any line-level corrections from other
|
|
11
|
+
# linters are applied before the trailing newline is normalized.
|
|
12
|
+
autocorrect_priority(1)
|
|
13
|
+
|
|
8
14
|
def visit_root(root)
|
|
9
15
|
return if document.source.empty?
|
|
10
16
|
line_number = document.last_non_empty_line
|
|
@@ -12,14 +18,32 @@ module HamlLint
|
|
|
12
18
|
node = root.node_for_line(line_number)
|
|
13
19
|
return if node.disabled?(self)
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
present = config['present'] ? true : false
|
|
22
|
+
corrected = corrected_source(present)
|
|
23
|
+
return if document.source == corrected
|
|
24
|
+
|
|
25
|
+
record_lint(line_number, message_for(present), corrected: autocorrect?)
|
|
26
|
+
apply_autocorrect(corrected)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def message_for(present)
|
|
32
|
+
if present
|
|
33
|
+
'Files should end with a trailing newline'
|
|
34
|
+
else
|
|
35
|
+
'Files should not end with a trailing newline'
|
|
36
|
+
end
|
|
37
|
+
end
|
|
16
38
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
39
|
+
# Normalizes only the single final newline. Collapsing multiple trailing
|
|
40
|
+
# newlines is `TrailingEmptyLines`' job, so a file that already ends with a
|
|
41
|
+
# newline is left untouched here under `present`.
|
|
42
|
+
def corrected_source(present)
|
|
43
|
+
if present
|
|
44
|
+
document.source.end_with?("\n") ? document.source : "#{document.source}\n"
|
|
45
|
+
else
|
|
46
|
+
document.source.sub(/\n+\z/, '')
|
|
23
47
|
end
|
|
24
48
|
end
|
|
25
49
|
end
|
|
@@ -6,6 +6,8 @@ module HamlLint
|
|
|
6
6
|
class Linter::ImplicitDiv < Linter
|
|
7
7
|
include LinterRegistry
|
|
8
8
|
|
|
9
|
+
supports_autocorrect(true)
|
|
10
|
+
|
|
9
11
|
def visit_tag(node)
|
|
10
12
|
return unless node.tag_name == 'div'
|
|
11
13
|
|
|
@@ -14,9 +16,20 @@ module HamlLint
|
|
|
14
16
|
tag = node.source_code[/\s*([^\s={(\[]+)/, 1]
|
|
15
17
|
return unless tag.start_with?('%div')
|
|
16
18
|
|
|
19
|
+
corrected = correct_implicit_div(node)
|
|
17
20
|
record_lint(node,
|
|
18
21
|
"`#{tag}` can be written as `#{node.static_attributes_source}` " \
|
|
19
|
-
'since `%div` is implicit'
|
|
22
|
+
'since `%div` is implicit',
|
|
23
|
+
corrected: corrected)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
# @return [Boolean]
|
|
29
|
+
def correct_implicit_div(node)
|
|
30
|
+
index = node.line - 1
|
|
31
|
+
line = autocorrected_lines[index]
|
|
32
|
+
correct_line(index, line.sub(/\A(\s*)%div(?=[.#])/, '\1'))
|
|
20
33
|
end
|
|
21
34
|
end
|
|
22
35
|
end
|
|
@@ -5,12 +5,24 @@ module HamlLint
|
|
|
5
5
|
class Linter::LeadingCommentSpace < Linter
|
|
6
6
|
include LinterRegistry
|
|
7
7
|
|
|
8
|
+
supports_autocorrect(true)
|
|
9
|
+
|
|
8
10
|
def visit_haml_comment(node)
|
|
9
11
|
# Skip if the node spans multiple lines starting on the second line,
|
|
10
12
|
# or starts with a space
|
|
11
13
|
return if /\A#*(\s*|\s+\S.*)$/.match?(node.text)
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
corrected = correct_leading_space(node)
|
|
16
|
+
record_lint(node, 'Comment should have a space after the `#`', corrected: corrected)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
# @return [Boolean]
|
|
22
|
+
def correct_leading_space(node)
|
|
23
|
+
index = node.line - 1
|
|
24
|
+
line = autocorrected_lines[index]
|
|
25
|
+
correct_line(index, line.sub(/\A(\s*-#+)(?=\S)/, '\1 '))
|
|
14
26
|
end
|
|
15
27
|
end
|
|
16
28
|
end
|