erb_lint 0.0.37 → 0.1.2
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/exe/erblint +1 -1
- data/lib/erb_lint/all.rb +26 -0
- data/lib/erb_lint/cli.rb +75 -29
- data/lib/erb_lint/corrector.rb +1 -1
- data/lib/erb_lint/linter.rb +5 -5
- data/lib/erb_lint/linter_config.rb +5 -3
- data/lib/erb_lint/linter_registry.rb +2 -2
- data/lib/erb_lint/linters/allowed_script_type.rb +9 -8
- data/lib/erb_lint/linters/closing_erb_tag_indent.rb +2 -2
- data/lib/erb_lint/linters/deprecated_classes.rb +8 -7
- data/lib/erb_lint/linters/erb_safety.rb +2 -2
- data/lib/erb_lint/linters/extra_newline.rb +1 -1
- data/lib/erb_lint/linters/final_newline.rb +2 -2
- data/lib/erb_lint/linters/hard_coded_string.rb +39 -16
- data/lib/erb_lint/linters/no_javascript_tag_helper.rb +11 -9
- data/lib/erb_lint/linters/partial_instance_variable.rb +23 -0
- data/lib/erb_lint/linters/require_input_autocomplete.rb +121 -0
- data/lib/erb_lint/linters/require_script_nonce.rb +92 -0
- data/lib/erb_lint/linters/right_trim.rb +1 -1
- data/lib/erb_lint/linters/rubocop.rb +13 -11
- data/lib/erb_lint/linters/rubocop_text.rb +1 -1
- data/lib/erb_lint/linters/self_closing_tag.rb +6 -7
- data/lib/erb_lint/linters/space_around_erb_tag.rb +8 -7
- data/lib/erb_lint/linters/space_in_html_tag.rb +7 -6
- data/lib/erb_lint/linters/space_indentation.rb +1 -1
- data/lib/erb_lint/linters/trailing_whitespace.rb +1 -1
- data/lib/erb_lint/offense.rb +8 -4
- data/lib/erb_lint/reporter.rb +4 -2
- data/lib/erb_lint/reporters/compact_reporter.rb +17 -4
- data/lib/erb_lint/reporters/json_reporter.rb +72 -0
- data/lib/erb_lint/reporters/multiline_reporter.rb +2 -1
- data/lib/erb_lint/runner.rb +3 -2
- data/lib/erb_lint/runner_config.rb +9 -7
- data/lib/erb_lint/runner_config_resolver.rb +5 -4
- data/lib/erb_lint/stats.rb +13 -6
- data/lib/erb_lint/utils/block_map.rb +3 -2
- data/lib/erb_lint/utils/offset_corrector.rb +2 -2
- data/lib/erb_lint/utils/ruby_to_erb.rb +6 -5
- data/lib/erb_lint/utils/severity_levels.rb +16 -0
- data/lib/erb_lint/version.rb +1 -1
- data/lib/erb_lint.rb +1 -24
- metadata +23 -17
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
3
|
+
require "better_html"
|
4
|
+
require "better_html/ast/node"
|
5
|
+
require "better_html/test_helper/ruby_node"
|
6
|
+
require "erb_lint/utils/block_map"
|
7
|
+
require "erb_lint/utils/ruby_to_erb"
|
8
8
|
|
9
9
|
module ERBLint
|
10
10
|
module Linters
|
@@ -21,7 +21,8 @@ module ERBLint
|
|
21
21
|
parser.ast.descendants(:erb).each do |erb_node|
|
22
22
|
indicator_node, _, code_node, _ = *erb_node
|
23
23
|
indicator = indicator_node&.loc&.source
|
24
|
-
next if indicator ==
|
24
|
+
next if indicator == "#"
|
25
|
+
|
25
26
|
source = code_node.loc.source
|
26
27
|
|
27
28
|
ruby_node =
|
@@ -31,13 +32,14 @@ module ERBLint
|
|
31
32
|
nil
|
32
33
|
end
|
33
34
|
next unless ruby_node
|
35
|
+
|
34
36
|
send_node = ruby_node.descendants(:send).first
|
35
37
|
next unless send_node&.method_name?(:javascript_tag)
|
36
38
|
|
37
39
|
add_offense(
|
38
40
|
erb_node.loc,
|
39
41
|
"Avoid using 'javascript_tag do' as it confuses tests "\
|
40
|
-
|
42
|
+
"that validate html, use inline <script> instead",
|
41
43
|
[erb_node, send_node]
|
42
44
|
)
|
43
45
|
end
|
@@ -63,10 +65,10 @@ module ERBLint
|
|
63
65
|
return unless (0..2).cover?(argument_nodes.size)
|
64
66
|
|
65
67
|
script_content = unless argument_nodes.first&.type?(:hash)
|
66
|
-
Utils::RubyToERB.ruby_to_erb(argument_nodes.first,
|
68
|
+
Utils::RubyToERB.ruby_to_erb(argument_nodes.first, "==")
|
67
69
|
end
|
68
70
|
arguments = if argument_nodes.last&.type?(:hash)
|
69
|
-
|
71
|
+
" " + Utils::RubyToERB.html_options_to_tag_attributes(argument_nodes.last)
|
70
72
|
end
|
71
73
|
|
72
74
|
return if end_node && script_content
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ERBLint
|
4
|
+
module Linters
|
5
|
+
# Checks for instance variables in partials.
|
6
|
+
class PartialInstanceVariable < Linter
|
7
|
+
include LinterRegistry
|
8
|
+
|
9
|
+
def run(processed_source)
|
10
|
+
instance_variable_regex = /\s@\w+/
|
11
|
+
return unless processed_source.filename.match?(%r{(\A|.*/)_[^/\s]*\.html\.erb\z}) &&
|
12
|
+
processed_source.file_content.match?(instance_variable_regex)
|
13
|
+
|
14
|
+
add_offense(
|
15
|
+
processed_source.to_source_range(
|
16
|
+
processed_source.file_content =~ instance_variable_regex..processed_source.file_content.size
|
17
|
+
),
|
18
|
+
"Instance variable detected in partial."
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "better_html"
|
4
|
+
require "better_html/tree/tag"
|
5
|
+
|
6
|
+
module ERBLint
|
7
|
+
module Linters
|
8
|
+
class RequireInputAutocomplete < Linter
|
9
|
+
include LinterRegistry
|
10
|
+
|
11
|
+
HTML_INPUT_TYPES_REQUIRING_AUTOCOMPLETE = [
|
12
|
+
"color",
|
13
|
+
"date",
|
14
|
+
"datetime-local",
|
15
|
+
"email",
|
16
|
+
"month",
|
17
|
+
"number",
|
18
|
+
"password",
|
19
|
+
"range",
|
20
|
+
"search",
|
21
|
+
"tel",
|
22
|
+
"text",
|
23
|
+
"time",
|
24
|
+
"url",
|
25
|
+
"week",
|
26
|
+
].freeze
|
27
|
+
|
28
|
+
FORM_HELPERS_REQUIRING_AUTOCOMPLETE = [
|
29
|
+
:date_field_tag,
|
30
|
+
:color_field_tag,
|
31
|
+
:email_field_tag,
|
32
|
+
:text_field_tag,
|
33
|
+
:utf8_enforcer_tag,
|
34
|
+
:month_field_tag,
|
35
|
+
:number_field_tag,
|
36
|
+
:password_field_tag,
|
37
|
+
:search_field_tag,
|
38
|
+
:telephone_field_tag,
|
39
|
+
:time_field_tag,
|
40
|
+
:url_field_tag,
|
41
|
+
:week_field_tag,
|
42
|
+
].freeze
|
43
|
+
|
44
|
+
def run(processed_source)
|
45
|
+
parser = processed_source.parser
|
46
|
+
|
47
|
+
find_html_input_tags(parser)
|
48
|
+
find_rails_helper_input_tags(parser)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def find_html_input_tags(parser)
|
54
|
+
parser.nodes_with_type(:tag).each do |tag_node|
|
55
|
+
tag = BetterHtml::Tree::Tag.from_node(tag_node)
|
56
|
+
|
57
|
+
autocomplete_attribute = tag.attributes["autocomplete"]
|
58
|
+
type_attribute = tag.attributes["type"]
|
59
|
+
|
60
|
+
next if !html_input_tag?(tag) || autocomplete_present?(autocomplete_attribute)
|
61
|
+
next unless html_type_requires_autocomplete_attribute?(type_attribute)
|
62
|
+
|
63
|
+
add_offense(
|
64
|
+
tag_node.to_a[1].loc,
|
65
|
+
"Input tag is missing an autocomplete attribute. If no "\
|
66
|
+
"autocomplete behaviour is desired, use the value `off` or `nope`.",
|
67
|
+
[autocomplete_attribute]
|
68
|
+
)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def autocomplete_present?(autocomplete_attribute)
|
73
|
+
autocomplete_attribute.present? && autocomplete_attribute.value_node.present?
|
74
|
+
end
|
75
|
+
|
76
|
+
def html_input_tag?(tag)
|
77
|
+
!tag.closing? && tag.name == "input"
|
78
|
+
end
|
79
|
+
|
80
|
+
def html_type_requires_autocomplete_attribute?(type_attribute)
|
81
|
+
type_present = type_attribute.present? && type_attribute.value_node.present?
|
82
|
+
type_present && HTML_INPUT_TYPES_REQUIRING_AUTOCOMPLETE.include?(type_attribute.value)
|
83
|
+
end
|
84
|
+
|
85
|
+
def find_rails_helper_input_tags(parser)
|
86
|
+
parser.ast.descendants(:erb).each do |erb_node|
|
87
|
+
indicator_node, _, code_node, _ = *erb_node
|
88
|
+
source = code_node.loc.source
|
89
|
+
ruby_node = extract_ruby_node(source)
|
90
|
+
send_node = ruby_node&.descendants(:send)&.first
|
91
|
+
|
92
|
+
next if code_comment?(indicator_node) ||
|
93
|
+
!ruby_node ||
|
94
|
+
!input_helper?(send_node) ||
|
95
|
+
source.include?("autocomplete")
|
96
|
+
|
97
|
+
add_offense(
|
98
|
+
erb_node.loc,
|
99
|
+
"Input field helper is missing an autocomplete attribute. If no "\
|
100
|
+
"autocomplete behaviour is desired, use the value `off` or `nope`.",
|
101
|
+
[erb_node, send_node]
|
102
|
+
)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def input_helper?(send_node)
|
107
|
+
FORM_HELPERS_REQUIRING_AUTOCOMPLETE.include?(send_node&.method_name)
|
108
|
+
end
|
109
|
+
|
110
|
+
def code_comment?(indicator_node)
|
111
|
+
indicator_node&.loc&.source == "#"
|
112
|
+
end
|
113
|
+
|
114
|
+
def extract_ruby_node(source)
|
115
|
+
BetterHtml::TestHelper::RubyNode.parse(source)
|
116
|
+
rescue ::Parser::SyntaxError
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "better_html"
|
4
|
+
require "better_html/tree/tag"
|
5
|
+
|
6
|
+
module ERBLint
|
7
|
+
module Linters
|
8
|
+
# Allow inline script tags in ERB that have a nonce attribute.
|
9
|
+
# This only validates inline <script> tags, as well as rails helpers like javascript_tag.
|
10
|
+
class RequireScriptNonce < Linter
|
11
|
+
include LinterRegistry
|
12
|
+
|
13
|
+
def run(processed_source)
|
14
|
+
parser = processed_source.parser
|
15
|
+
|
16
|
+
find_html_script_tags(parser)
|
17
|
+
find_rails_helper_script_tags(parser)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def find_html_script_tags(parser)
|
23
|
+
parser.nodes_with_type(:tag).each do |tag_node|
|
24
|
+
tag = BetterHtml::Tree::Tag.from_node(tag_node)
|
25
|
+
nonce_attribute = tag.attributes["nonce"]
|
26
|
+
|
27
|
+
next if !html_javascript_tag?(tag) || nonce_present?(nonce_attribute)
|
28
|
+
|
29
|
+
add_offense(
|
30
|
+
tag_node.to_a[1].loc,
|
31
|
+
"Missing a nonce attribute. Use request.content_security_policy_nonce",
|
32
|
+
[nonce_attribute]
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def nonce_present?(nonce_attribute)
|
38
|
+
nonce_attribute.present? && nonce_attribute.value_node.present?
|
39
|
+
end
|
40
|
+
|
41
|
+
def html_javascript_tag?(tag)
|
42
|
+
!tag.closing? &&
|
43
|
+
(tag.name == "script" && !html_javascript_type_attribute?(tag))
|
44
|
+
end
|
45
|
+
|
46
|
+
def html_javascript_type_attribute?(tag)
|
47
|
+
type_attribute = tag.attributes["type"]
|
48
|
+
|
49
|
+
type_attribute &&
|
50
|
+
type_attribute.value_node.present? &&
|
51
|
+
type_attribute.value_node.to_a[1] != "text/javascript" &&
|
52
|
+
type_attribute.value_node.to_a[1] != "application/javascript"
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_rails_helper_script_tags(parser)
|
56
|
+
parser.ast.descendants(:erb).each do |erb_node|
|
57
|
+
indicator_node, _, code_node, _ = *erb_node
|
58
|
+
source = code_node.loc.source
|
59
|
+
ruby_node = extract_ruby_node(source)
|
60
|
+
send_node = ruby_node&.descendants(:send)&.first
|
61
|
+
|
62
|
+
next if code_comment?(indicator_node) ||
|
63
|
+
!ruby_node ||
|
64
|
+
!tag_helper?(send_node) ||
|
65
|
+
source.include?("nonce")
|
66
|
+
|
67
|
+
add_offense(
|
68
|
+
erb_node.loc,
|
69
|
+
"Missing a nonce attribute. Use nonce: true",
|
70
|
+
[erb_node, send_node]
|
71
|
+
)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def tag_helper?(send_node)
|
76
|
+
send_node&.method_name?(:javascript_tag) ||
|
77
|
+
send_node&.method_name?(:javascript_include_tag) ||
|
78
|
+
send_node&.method_name?(:javascript_pack_tag)
|
79
|
+
end
|
80
|
+
|
81
|
+
def code_comment?(indicator_node)
|
82
|
+
indicator_node&.loc&.source == "#"
|
83
|
+
end
|
84
|
+
|
85
|
+
def extract_ruby_node(source)
|
86
|
+
BetterHtml::TestHelper::RubyNode.parse(source)
|
87
|
+
rescue ::Parser::SyntaxError
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "better_html"
|
4
|
+
require "tempfile"
|
5
|
+
require "erb_lint/utils/offset_corrector"
|
6
6
|
|
7
7
|
module ERBLint
|
8
8
|
module Linters
|
@@ -25,7 +25,7 @@ module ERBLint
|
|
25
25
|
super
|
26
26
|
@only_cops = @config.only
|
27
27
|
custom_config = config_from_hash(@config.rubocop_config)
|
28
|
-
@rubocop_config = ::RuboCop::ConfigLoader.merge_with_default(custom_config,
|
28
|
+
@rubocop_config = ::RuboCop::ConfigLoader.merge_with_default(custom_config, "")
|
29
29
|
end
|
30
30
|
|
31
31
|
def run(processed_source)
|
@@ -37,6 +37,7 @@ module ERBLint
|
|
37
37
|
if ::RuboCop::Version::STRING.to_f >= 0.87
|
38
38
|
def autocorrect(_processed_source, offense)
|
39
39
|
return unless offense.context
|
40
|
+
|
40
41
|
rubocop_correction = offense.context[:rubocop_correction]
|
41
42
|
return unless rubocop_correction
|
42
43
|
|
@@ -68,13 +69,13 @@ module ERBLint
|
|
68
69
|
|
69
70
|
def inspect_content(processed_source, erb_node)
|
70
71
|
indicator, _, code_node, = *erb_node
|
71
|
-
return if indicator&.children&.first ==
|
72
|
+
return if indicator&.children&.first == "#"
|
72
73
|
|
73
74
|
original_source = code_node.loc.source
|
74
|
-
trimmed_source = original_source.sub(BLOCK_EXPR,
|
75
|
+
trimmed_source = original_source.sub(BLOCK_EXPR, "").sub(SUFFIX_EXPR, "")
|
75
76
|
alignment_column = code_node.loc.column
|
76
77
|
offset = code_node.loc.begin_pos - alignment_column
|
77
|
-
aligned_source = "#{
|
78
|
+
aligned_source = "#{" " * alignment_column}#{trimmed_source}"
|
78
79
|
|
79
80
|
source = rubocop_processed_source(aligned_source, processed_source.filename)
|
80
81
|
return unless source.valid_syntax?
|
@@ -150,16 +151,17 @@ module ERBLint
|
|
150
151
|
@rubocop_config,
|
151
152
|
extra_details: true,
|
152
153
|
display_cop_names: true,
|
154
|
+
autocorrect: true,
|
153
155
|
auto_correct: true,
|
154
156
|
stdin: "",
|
155
157
|
)
|
156
158
|
end
|
157
159
|
|
158
160
|
def config_from_hash(hash)
|
159
|
-
inherit_from = hash&.delete(
|
161
|
+
inherit_from = hash&.delete("inherit_from")
|
160
162
|
resolve_inheritance(hash, inherit_from)
|
161
163
|
|
162
|
-
tempfile_from(
|
164
|
+
tempfile_from(".erblint-rubocop", hash.to_yaml) do |tempfile|
|
163
165
|
::RuboCop::ConfigLoader.load_file(tempfile.path)
|
164
166
|
end
|
165
167
|
end
|
@@ -174,7 +176,7 @@ module ERBLint
|
|
174
176
|
end
|
175
177
|
|
176
178
|
def base_configs(inherit_from)
|
177
|
-
regex = URI::DEFAULT_PARSER.make_regexp(
|
179
|
+
regex = URI::DEFAULT_PARSER.make_regexp(["http", "https"])
|
178
180
|
configs = Array(inherit_from).compact.map do |base_name|
|
179
181
|
if base_name =~ /\A#{regex}\z/
|
180
182
|
::RuboCop::ConfigLoader.load_file(::RuboCop::RemoteConfig.new(base_name, Dir.pwd))
|
@@ -191,7 +193,7 @@ module ERBLint
|
|
191
193
|
{ rubocop_correction: correction, offset: offset, bound_range: bound_range }
|
192
194
|
end
|
193
195
|
|
194
|
-
super(offense_range, rubocop_offense.message.strip, context)
|
196
|
+
super(offense_range, rubocop_offense.message.strip, context, rubocop_offense.severity.name)
|
195
197
|
end
|
196
198
|
end
|
197
199
|
end
|
@@ -11,10 +11,8 @@ module ERBLint
|
|
11
11
|
end
|
12
12
|
self.config_schema = ConfigSchema
|
13
13
|
|
14
|
-
SELF_CLOSING_TAGS =
|
15
|
-
|
16
|
-
link menuitem meta param source track wbr img
|
17
|
-
)
|
14
|
+
SELF_CLOSING_TAGS = ["area", "base", "br", "col", "command", "embed", "hr", "input", "keygen", "link",
|
15
|
+
"menuitem", "meta", "param", "source", "track", "wbr", "img",]
|
18
16
|
|
19
17
|
def run(processed_source)
|
20
18
|
processed_source.ast.descendants(:tag).each do |tag_node|
|
@@ -26,7 +24,7 @@ module ERBLint
|
|
26
24
|
add_offense(
|
27
25
|
start_solidus.loc,
|
28
26
|
"Tag `#{tag.name}` is a void element, it must not start with `</`.",
|
29
|
-
|
27
|
+
""
|
30
28
|
)
|
31
29
|
end
|
32
30
|
|
@@ -34,16 +32,17 @@ module ERBLint
|
|
34
32
|
add_offense(
|
35
33
|
tag_node.loc.end.offset(-1),
|
36
34
|
"Tag `#{tag.name}` is self-closing, it must end with `/>`.",
|
37
|
-
|
35
|
+
"/"
|
38
36
|
)
|
39
37
|
end
|
40
38
|
|
41
39
|
next unless @config.enforced_style == :never && tag.self_closing?
|
40
|
+
|
42
41
|
end_solidus = tag_node.children.last
|
43
42
|
add_offense(
|
44
43
|
end_solidus.loc,
|
45
44
|
"Tag `#{tag.name}` is a void element, it must end with `>` and not `/>`.",
|
46
|
-
|
45
|
+
""
|
47
46
|
)
|
48
47
|
end
|
49
48
|
end
|
@@ -15,7 +15,8 @@ module ERBLint
|
|
15
15
|
processed_source.ast.descendants(:erb).each do |erb_node|
|
16
16
|
indicator_node, ltrim, code_node, rtrim = *erb_node
|
17
17
|
indicator = indicator_node&.loc&.source
|
18
|
-
next if indicator ==
|
18
|
+
next if indicator == "#" || indicator == "%"
|
19
|
+
|
19
20
|
code = code_node.children.first
|
20
21
|
|
21
22
|
start_spaces = code.match(START_SPACES)&.captures&.first || ""
|
@@ -23,15 +24,15 @@ module ERBLint
|
|
23
24
|
add_offense(
|
24
25
|
code_node.loc.resize(start_spaces.size),
|
25
26
|
"Use 1 space after `<%#{indicator}#{ltrim&.loc&.source}` "\
|
26
|
-
|
27
|
-
|
27
|
+
"instead of #{start_spaces.size} space#{"s" if start_spaces.size > 1}.",
|
28
|
+
" "
|
28
29
|
)
|
29
30
|
elsif start_spaces.count("\n") > 1
|
30
31
|
lines = start_spaces.split("\n", -1)
|
31
32
|
add_offense(
|
32
33
|
code_node.loc.resize(start_spaces.size),
|
33
34
|
"Use 1 newline after `<%#{indicator&.loc&.source}#{ltrim&.loc&.source}` "\
|
34
|
-
|
35
|
+
"instead of #{start_spaces.count("\n")}.",
|
35
36
|
"#{lines.first}\n#{lines.last}"
|
36
37
|
)
|
37
38
|
end
|
@@ -41,15 +42,15 @@ module ERBLint
|
|
41
42
|
add_offense(
|
42
43
|
code_node.loc.end.adjust(begin_pos: -end_spaces.size),
|
43
44
|
"Use 1 space before `#{rtrim&.loc&.source}%>` "\
|
44
|
-
|
45
|
-
|
45
|
+
"instead of #{end_spaces.size} space#{"s" if start_spaces.size > 1}.",
|
46
|
+
" "
|
46
47
|
)
|
47
48
|
elsif end_spaces.count("\n") > 1
|
48
49
|
lines = end_spaces.split("\n", -1)
|
49
50
|
add_offense(
|
50
51
|
code_node.loc.end.adjust(begin_pos: -end_spaces.size),
|
51
52
|
"Use 1 newline before `#{rtrim&.loc&.source}%>` "\
|
52
|
-
|
53
|
+
"instead of #{end_spaces.count("\n")}.",
|
53
54
|
"#{lines.first}\n#{lines.last}"
|
54
55
|
)
|
55
56
|
end
|
@@ -50,7 +50,7 @@ module ERBLint
|
|
50
50
|
add_offense(
|
51
51
|
processed_source.to_source_range(range),
|
52
52
|
"Extra space detected where there should be no space.",
|
53
|
-
|
53
|
+
""
|
54
54
|
)
|
55
55
|
end
|
56
56
|
|
@@ -60,24 +60,24 @@ module ERBLint
|
|
60
60
|
|
61
61
|
def single_space(processed_source, range, accept_newline: false)
|
62
62
|
chars = processed_source.file_content[range]
|
63
|
-
return if chars ==
|
63
|
+
return if chars == " "
|
64
64
|
|
65
65
|
newlines = chars.include?("\n")
|
66
|
-
expected = newlines && accept_newline ? "\n#{chars.split("\n", -1).last}" :
|
66
|
+
expected = newlines && accept_newline ? "\n#{chars.split("\n", -1).last}" : " "
|
67
67
|
non_space = chars.match(/([^[[:space:]]])/m)
|
68
68
|
|
69
69
|
if non_space && !non_space.captures.empty?
|
70
70
|
add_offense(
|
71
71
|
processed_source.to_source_range(range),
|
72
72
|
"Non-whitespace character(s) detected: "\
|
73
|
-
"#{non_space.captures.map(&:inspect).join(
|
73
|
+
"#{non_space.captures.map(&:inspect).join(", ")}.",
|
74
74
|
expected
|
75
75
|
)
|
76
76
|
elsif newlines && accept_newline
|
77
77
|
if expected != chars
|
78
78
|
add_offense(
|
79
79
|
processed_source.to_source_range(range),
|
80
|
-
"#{chars.empty? ?
|
80
|
+
"#{chars.empty? ? "No" : "Extra"} space detected where there should be "\
|
81
81
|
"a single space or a single line break.",
|
82
82
|
expected
|
83
83
|
)
|
@@ -85,7 +85,7 @@ module ERBLint
|
|
85
85
|
else
|
86
86
|
add_offense(
|
87
87
|
processed_source.to_source_range(range),
|
88
|
-
"#{chars.empty? ?
|
88
|
+
"#{chars.empty? ? "No" : "Extra"} space detected where there should be a single space.",
|
89
89
|
expected
|
90
90
|
)
|
91
91
|
end
|
@@ -98,6 +98,7 @@ module ERBLint
|
|
98
98
|
no_space(processed_source, equal.loc.end_pos...value.loc.begin_pos) if equal && value
|
99
99
|
|
100
100
|
next if index >= attributes.children.size - 1
|
101
|
+
|
101
102
|
next_attribute = attributes.children[index + 1]
|
102
103
|
|
103
104
|
single_space_or_newline(
|
@@ -23,7 +23,7 @@ module ERBLint
|
|
23
23
|
add_offense(
|
24
24
|
processed_source.to_source_range(document_pos...(document_pos + spaces.length)),
|
25
25
|
"Indent with spaces instead of tabs.",
|
26
|
-
spaces.gsub("\t",
|
26
|
+
spaces.gsub("\t", " " * @config.tab_width)
|
27
27
|
)
|
28
28
|
end
|
29
29
|
|
data/lib/erb_lint/offense.rb
CHANGED
@@ -3,29 +3,33 @@
|
|
3
3
|
module ERBLint
|
4
4
|
# Defines common functionality available to all linters.
|
5
5
|
class Offense
|
6
|
-
attr_reader :linter, :source_range, :message, :context
|
6
|
+
attr_reader :linter, :source_range, :message, :context, :severity
|
7
7
|
|
8
|
-
def initialize(linter, source_range, message, context = nil)
|
8
|
+
def initialize(linter, source_range, message, context = nil, severity = nil)
|
9
9
|
unless source_range.is_a?(Parser::Source::Range)
|
10
10
|
raise ArgumentError, "expected Parser::Source::Range for arg 2"
|
11
11
|
end
|
12
|
+
|
12
13
|
@linter = linter
|
13
14
|
@source_range = source_range
|
14
15
|
@message = message
|
15
16
|
@context = context
|
17
|
+
@severity = severity
|
16
18
|
end
|
17
19
|
|
18
20
|
def inspect
|
19
21
|
"#<#{self.class.name} linter=#{linter.class.name} "\
|
20
22
|
"source_range=#{source_range.begin_pos}...#{source_range.end_pos} "\
|
21
|
-
"message=#{message}>"
|
23
|
+
"message=#{message}> "\
|
24
|
+
"severity=#{severity}"
|
22
25
|
end
|
23
26
|
|
24
27
|
def ==(other)
|
25
28
|
other.class <= ERBLint::Offense &&
|
26
29
|
other.linter == linter &&
|
27
30
|
other.source_range == source_range &&
|
28
|
-
other.message == message
|
31
|
+
other.message == message &&
|
32
|
+
other.severity == severity
|
29
33
|
end
|
30
34
|
|
31
35
|
def line_range
|
data/lib/erb_lint/reporter.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require
|
2
|
+
|
3
|
+
require "active_support/core_ext/class"
|
4
|
+
require "active_support/core_ext/module/delegation"
|
4
5
|
|
5
6
|
module ERBLint
|
6
7
|
class Reporter
|
@@ -34,6 +35,7 @@ module ERBLint
|
|
34
35
|
private
|
35
36
|
|
36
37
|
attr_reader :stats, :autocorrect
|
38
|
+
|
37
39
|
delegate :processed_files, to: :stats
|
38
40
|
end
|
39
41
|
end
|
@@ -4,8 +4,7 @@ module ERBLint
|
|
4
4
|
module Reporters
|
5
5
|
class CompactReporter < Reporter
|
6
6
|
def preview
|
7
|
-
puts "
|
8
|
-
"#{stats.linters} #{'autocorrectable ' if autocorrect}linters..."
|
7
|
+
puts "#{linting} #{stats.files} files with #{linters}..."
|
9
8
|
end
|
10
9
|
|
11
10
|
def show
|
@@ -21,6 +20,14 @@ module ERBLint
|
|
21
20
|
|
22
21
|
private
|
23
22
|
|
23
|
+
def linting
|
24
|
+
"Linting" + (autocorrect ? " and autocorrecting" : "")
|
25
|
+
end
|
26
|
+
|
27
|
+
def linters
|
28
|
+
"#{stats.linters} linters" + (autocorrect ? " (#{stats.autocorrectable_linters} autocorrectable)" : "")
|
29
|
+
end
|
30
|
+
|
24
31
|
def format_offense(filename, offense)
|
25
32
|
[
|
26
33
|
"#{filename}:",
|
@@ -35,8 +42,14 @@ module ERBLint
|
|
35
42
|
def summary
|
36
43
|
if stats.corrected > 0
|
37
44
|
report_corrected_offenses
|
38
|
-
elsif stats.found > 0
|
39
|
-
|
45
|
+
elsif stats.ignored > 0 || stats.found > 0
|
46
|
+
if stats.ignored > 0
|
47
|
+
warn(Rainbow("#{stats.ignored} error(s) were ignored in ERB files").yellow)
|
48
|
+
end
|
49
|
+
|
50
|
+
if stats.found > 0
|
51
|
+
warn(Rainbow("#{stats.found} error(s) were found in ERB files").red)
|
52
|
+
end
|
40
53
|
else
|
41
54
|
puts Rainbow("No errors were found in ERB files").green
|
42
55
|
end
|