erb_lint 0.1.0 → 0.1.1
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.rb +1 -1
- data/lib/erb_lint/all.rb +15 -15
- data/lib/erb_lint/cli.rb +18 -18
- data/lib/erb_lint/corrector.rb +1 -1
- data/lib/erb_lint/linter.rb +3 -3
- data/lib/erb_lint/linter_config.rb +3 -3
- data/lib/erb_lint/linter_registry.rb +2 -2
- data/lib/erb_lint/linters/allowed_script_type.rb +7 -7
- data/lib/erb_lint/linters/closing_erb_tag_indent.rb +2 -2
- data/lib/erb_lint/linters/deprecated_classes.rb +7 -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 +36 -36
- data/lib/erb_lint/linters/no_javascript_tag_helper.rb +8 -8
- data/lib/erb_lint/linters/require_input_autocomplete.rb +6 -8
- data/lib/erb_lint/linters/require_script_nonce.rb +8 -8
- data/lib/erb_lint/linters/right_trim.rb +1 -1
- data/lib/erb_lint/linters/rubocop.rb +10 -10
- data/lib/erb_lint/linters/rubocop_text.rb +1 -1
- data/lib/erb_lint/linters/self_closing_tag.rb +5 -7
- data/lib/erb_lint/linters/space_around_erb_tag.rb +5 -5
- data/lib/erb_lint/linters/space_in_html_tag.rb +6 -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/reporter.rb +2 -2
- data/lib/erb_lint/reporters/compact_reporter.rb +1 -1
- data/lib/erb_lint/reporters/multiline_reporter.rb +1 -1
- data/lib/erb_lint/runner.rb +1 -1
- data/lib/erb_lint/runner_config.rb +7 -7
- data/lib/erb_lint/runner_config_resolver.rb +4 -4
- data/lib/erb_lint/stats.rb +6 -6
- data/lib/erb_lint/utils/block_map.rb +2 -2
- data/lib/erb_lint/utils/offset_corrector.rb +1 -1
- data/lib/erb_lint/utils/ruby_to_erb.rb +5 -5
- data/lib/erb_lint/utils/severity_levels.rb +2 -2
- data/lib/erb_lint/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b98507494f8af2a33e0e6fcbf28c48a2d91c78f42273a3cc6c1206578f3a55b0
|
4
|
+
data.tar.gz: c090a633a9d041a6d4d71bc292b1525b3a74dccd61311e0224bc1cbb0d8fea33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2ac77d88b7be36010d69c072e3ee72c370edefa1fc3b68135593b5b494da8b0f9eac79e9b5408d10155a007d94948ae6a4b3b279de2541be66f0576abc964a8
|
7
|
+
data.tar.gz: f0be6a75c5060b49a8b7c5e5a5d19adb3222fb580a11c2e2e488e5b83ada00db0c4daee25e99ba5538057c749f5937b80d25a96bda1ed967e6d1baeddf787965
|
data/exe/erblint
CHANGED
data/lib/erb_lint.rb
CHANGED
data/lib/erb_lint/all.rb
CHANGED
@@ -1,26 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "rubocop"
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require
|
5
|
+
require "erb_lint"
|
6
|
+
require "erb_lint/corrector"
|
7
|
+
require "erb_lint/file_loader"
|
8
|
+
require "erb_lint/linter_config"
|
9
|
+
require "erb_lint/linter_registry"
|
10
|
+
require "erb_lint/linter"
|
11
|
+
require "erb_lint/offense"
|
12
|
+
require "erb_lint/processed_source"
|
13
|
+
require "erb_lint/runner_config"
|
14
|
+
require "erb_lint/runner"
|
15
|
+
require "erb_lint/stats"
|
16
|
+
require "erb_lint/reporter"
|
17
17
|
|
18
18
|
# Load linters
|
19
|
-
Dir[File.expand_path(
|
19
|
+
Dir[File.expand_path("linters/**/*.rb", __dir__)].each do |file|
|
20
20
|
require file
|
21
21
|
end
|
22
22
|
|
23
23
|
# Load reporters
|
24
|
-
Dir[File.expand_path(
|
24
|
+
Dir[File.expand_path("reporters/**/*.rb", __dir__)].each do |file|
|
25
25
|
require file
|
26
26
|
end
|
data/lib/erb_lint/cli.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
3
|
+
require "erb_lint/all"
|
4
|
+
require "active_support"
|
5
|
+
require "active_support/inflector"
|
6
|
+
require "optparse"
|
7
|
+
require "psych"
|
8
|
+
require "yaml"
|
9
|
+
require "rainbow"
|
10
|
+
require "erb_lint/utils/severity_levels"
|
11
11
|
|
12
12
|
module ERBLint
|
13
13
|
class CLI
|
14
14
|
include Utils::SeverityLevels
|
15
15
|
|
16
|
-
DEFAULT_CONFIG_FILENAME =
|
16
|
+
DEFAULT_CONFIG_FILENAME = ".erb-lint.yml"
|
17
17
|
DEFAULT_LINT_ALL_GLOB = "**/*.html{+*,}.erb"
|
18
18
|
|
19
19
|
class ExitWithFailure < RuntimeError; end
|
@@ -43,7 +43,7 @@ module ERBLint
|
|
43
43
|
ensure_files_exist(lint_files)
|
44
44
|
|
45
45
|
if enabled_linter_classes.empty?
|
46
|
-
failure!(
|
46
|
+
failure!("no linter available with current configuration")
|
47
47
|
end
|
48
48
|
|
49
49
|
@options[:format] ||= :multiline
|
@@ -145,7 +145,7 @@ module ERBLint
|
|
145
145
|
|
146
146
|
def correct(processed_source, offenses)
|
147
147
|
corrector = ERBLint::Corrector.new(processed_source, offenses)
|
148
|
-
failure!(corrector.diagnostics.join(
|
148
|
+
failure!(corrector.diagnostics.join(", ")) if corrector.diagnostics.any?
|
149
149
|
corrector
|
150
150
|
end
|
151
151
|
|
@@ -183,7 +183,7 @@ module ERBLint
|
|
183
183
|
else
|
184
184
|
@files
|
185
185
|
.map { |f| Dir.exist?(f) ? Dir[File.join(f, glob)] : f }
|
186
|
-
.map { |f| f.include?(
|
186
|
+
.map { |f| f.include?("*") ? Dir[f] : f }
|
187
187
|
.flatten
|
188
188
|
.map { |f| File.expand_path(f, Dir.pwd) }
|
189
189
|
.select { |filename| !excluded?(filename) }
|
@@ -240,14 +240,14 @@ module ERBLint
|
|
240
240
|
end
|
241
241
|
|
242
242
|
def relative_filename(filename)
|
243
|
-
filename.sub("#{File.expand_path(
|
243
|
+
filename.sub("#{File.expand_path(".", Dir.pwd)}/", "")
|
244
244
|
end
|
245
245
|
|
246
246
|
def runner_config_override
|
247
247
|
RunnerConfig.new(
|
248
248
|
linters: {}.tap do |linters|
|
249
249
|
ERBLint::LinterRegistry.linters.map do |klass|
|
250
|
-
linters[klass.simple_name] = {
|
250
|
+
linters[klass.simple_name] = { "enabled" => enabled_linter_classes.include?(klass) }
|
251
251
|
end
|
252
252
|
end
|
253
253
|
)
|
@@ -283,10 +283,10 @@ module ERBLint
|
|
283
283
|
end
|
284
284
|
|
285
285
|
opts.on("--enable-linters LINTER[,LINTER,...]", Array,
|
286
|
-
"Only use specified linter", "Known linters are: #{known_linter_names.join(
|
286
|
+
"Only use specified linter", "Known linters are: #{known_linter_names.join(", ")}") do |linters|
|
287
287
|
linters.each do |linter|
|
288
288
|
unless known_linter_names.include?(linter)
|
289
|
-
failure!("#{linter}: not a valid linter name (#{known_linter_names.join(
|
289
|
+
failure!("#{linter}: not a valid linter name (#{known_linter_names.join(", ")})")
|
290
290
|
end
|
291
291
|
end
|
292
292
|
@options[:enabled_linters] = linters
|
@@ -296,7 +296,7 @@ module ERBLint
|
|
296
296
|
parsed_severity = SEVERITY_CODE_TABLE[level.upcase.to_sym] || (SEVERITY_NAMES & [level.downcase]).first
|
297
297
|
|
298
298
|
if parsed_severity.nil?
|
299
|
-
failure!("#{level}: not a valid failure level (#{SEVERITY_NAMES.join(
|
299
|
+
failure!("#{level}: not a valid failure level (#{SEVERITY_NAMES.join(", ")})")
|
300
300
|
end
|
301
301
|
@options[:fail_level] = severity_level_for_name(parsed_severity)
|
302
302
|
end
|
@@ -325,7 +325,7 @@ module ERBLint
|
|
325
325
|
|
326
326
|
def format_options_help
|
327
327
|
"Report offenses in the given format: "\
|
328
|
-
"(#{Reporter.available_formats.join(
|
328
|
+
"(#{Reporter.available_formats.join(", ")}) (default: multiline)"
|
329
329
|
end
|
330
330
|
|
331
331
|
def invalid_format_error_message(given_format)
|
data/lib/erb_lint/corrector.rb
CHANGED
data/lib/erb_lint/linter.rb
CHANGED
@@ -15,9 +15,9 @@ module ERBLint
|
|
15
15
|
# `ERBLint::Linters::Compass::Bar.simple_name` #=> "Compass::Bar"
|
16
16
|
def inherited(linter)
|
17
17
|
super
|
18
|
-
linter.simple_name = if linter.name.start_with?(
|
19
|
-
name_parts = linter.name.split(
|
20
|
-
name_parts[2..-1].join(
|
18
|
+
linter.simple_name = if linter.name.start_with?("ERBLint::Linters::")
|
19
|
+
name_parts = linter.name.split("::")
|
20
|
+
name_parts[2..-1].join("::")
|
21
21
|
else
|
22
22
|
linter.name
|
23
23
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "active_support"
|
4
|
+
require "smart_properties"
|
5
5
|
|
6
6
|
module ERBLint
|
7
7
|
class LinterConfig
|
@@ -27,7 +27,7 @@ module ERBLint
|
|
27
27
|
allowed_keys = self.class.properties.keys.map(&:to_s)
|
28
28
|
given_keys = config.keys
|
29
29
|
if (extra_keys = given_keys - allowed_keys).any?
|
30
|
-
raise Error, "Given key is not allowed: #{extra_keys.join(
|
30
|
+
raise Error, "Given key is not allowed: #{extra_keys.join(", ")}"
|
31
31
|
end
|
32
32
|
super(config)
|
33
33
|
rescue SmartProperties::InitializationError => e
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module ERBLint
|
4
4
|
# Stores all linters available to the application.
|
5
5
|
module LinterRegistry
|
6
|
-
CUSTOM_LINTERS_DIR =
|
6
|
+
CUSTOM_LINTERS_DIR = ".erb-linters"
|
7
7
|
@loaded_linters = []
|
8
8
|
|
9
9
|
class << self
|
@@ -27,7 +27,7 @@ module ERBLint
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def load_custom_linters(directory = CUSTOM_LINTERS_DIR)
|
30
|
-
ruby_files = Dir.glob(File.expand_path(File.join(directory,
|
30
|
+
ruby_files = Dir.glob(File.expand_path(File.join(directory, "**", "*.rb")))
|
31
31
|
ruby_files.each { |file| require file }
|
32
32
|
end
|
33
33
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "better_html"
|
4
|
+
require "better_html/tree/tag"
|
5
5
|
|
6
6
|
module ERBLint
|
7
7
|
module Linters
|
@@ -13,7 +13,7 @@ module ERBLint
|
|
13
13
|
|
14
14
|
class ConfigSchema < LinterConfig
|
15
15
|
property :allowed_types, accepts: array_of?(String),
|
16
|
-
default: -> { [
|
16
|
+
default: -> { ["text/javascript"] }
|
17
17
|
property :allow_blank, accepts: [true, false], default: true, reader: :allow_blank?
|
18
18
|
property :disallow_inline_scripts, accepts: [true, false], default: false, reader: :disallow_inline_scripts?
|
19
19
|
end
|
@@ -24,7 +24,7 @@ module ERBLint
|
|
24
24
|
parser.nodes_with_type(:tag).each do |tag_node|
|
25
25
|
tag = BetterHtml::Tree::Tag.from_node(tag_node)
|
26
26
|
next if tag.closing?
|
27
|
-
next unless tag.name ==
|
27
|
+
next unless tag.name == "script"
|
28
28
|
|
29
29
|
if @config.disallow_inline_scripts?
|
30
30
|
name_node = tag_node.to_a[1]
|
@@ -36,7 +36,7 @@ module ERBLint
|
|
36
36
|
next
|
37
37
|
end
|
38
38
|
|
39
|
-
type_attribute = tag.attributes[
|
39
|
+
type_attribute = tag.attributes["type"]
|
40
40
|
type_present = type_attribute.present? && type_attribute.value_node.present?
|
41
41
|
|
42
42
|
if !type_present && !@config.allow_blank?
|
@@ -50,8 +50,8 @@ module ERBLint
|
|
50
50
|
add_offense(
|
51
51
|
type_attribute.loc,
|
52
52
|
"Avoid using #{type_attribute.value.inspect} as type for `<script>` tag. "\
|
53
|
-
"Must be one of: #{@config.allowed_types.join(
|
54
|
-
"#{
|
53
|
+
"Must be one of: #{@config.allowed_types.join(", ")}"\
|
54
|
+
"#{" (or no type attribute)" if @config.allow_blank?}."
|
55
55
|
)
|
56
56
|
end
|
57
57
|
end
|
@@ -25,7 +25,7 @@ module ERBLint
|
|
25
25
|
add_offense(
|
26
26
|
code_node.loc.end.adjust(begin_pos: -end_spaces.size),
|
27
27
|
"Remove newline before `%>` to match start of tag.",
|
28
|
-
|
28
|
+
" "
|
29
29
|
)
|
30
30
|
elsif start_with_newline && !end_with_newline
|
31
31
|
add_offense(
|
@@ -39,7 +39,7 @@ module ERBLint
|
|
39
39
|
add_offense(
|
40
40
|
code_node.loc.end.adjust(begin_pos: -current_indent.size),
|
41
41
|
"Indent `%>` on column #{erb_node.loc.column} to match start of tag.",
|
42
|
-
|
42
|
+
" " * erb_node.loc.column
|
43
43
|
)
|
44
44
|
end
|
45
45
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "better_html"
|
4
|
+
require "better_html/parser"
|
5
5
|
|
6
6
|
module ERBLint
|
7
7
|
module Linters
|
@@ -11,7 +11,7 @@ module ERBLint
|
|
11
11
|
|
12
12
|
class RuleSet
|
13
13
|
include SmartProperties
|
14
|
-
property :suggestion, accepts: String, default:
|
14
|
+
property :suggestion, accepts: String, default: ""
|
15
15
|
property :deprecated, accepts: LinterConfig.array_of?(String), default: -> { [] }
|
16
16
|
end
|
17
17
|
|
@@ -57,9 +57,9 @@ module ERBLint
|
|
57
57
|
def class_name_with_loc(processed_source)
|
58
58
|
Enumerator.new do |yielder|
|
59
59
|
tags(processed_source).each do |tag|
|
60
|
-
class_value = tag.attributes[
|
60
|
+
class_value = tag.attributes["class"]&.value
|
61
61
|
next unless class_value
|
62
|
-
class_value.split(
|
62
|
+
class_value.split(" ").each do |class_name|
|
63
63
|
yielder.yield(class_name, tag.loc)
|
64
64
|
end
|
65
65
|
end
|
@@ -69,7 +69,7 @@ module ERBLint
|
|
69
69
|
def text_tags_content(processed_source)
|
70
70
|
Enumerator.new do |yielder|
|
71
71
|
script_tags(processed_source)
|
72
|
-
.select { |tag| tag.attributes[
|
72
|
+
.select { |tag| tag.attributes["type"]&.value == "text/html" }
|
73
73
|
.each do |tag|
|
74
74
|
index = processed_source.ast.to_a.find_index(tag.node)
|
75
75
|
next_node = processed_source.ast.to_a[index + 1]
|
@@ -80,7 +80,7 @@ module ERBLint
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def script_tags(processed_source)
|
83
|
-
tags(processed_source).select { |tag| tag.name ==
|
83
|
+
tags(processed_source).select { |tag| tag.name == "script" }
|
84
84
|
end
|
85
85
|
|
86
86
|
def tags(processed_source)
|
@@ -28,7 +28,7 @@ module ERBLint
|
|
28
28
|
if final_newline.empty?
|
29
29
|
add_offense(
|
30
30
|
processed_source.to_source_range(file_content.size...file_content.size),
|
31
|
-
|
31
|
+
"Missing a trailing newline at the end of the file.",
|
32
32
|
:insert
|
33
33
|
)
|
34
34
|
else
|
@@ -36,7 +36,7 @@ module ERBLint
|
|
36
36
|
processed_source.to_source_range(
|
37
37
|
(file_content.size - final_newline.size + 1)...file_content.size
|
38
38
|
),
|
39
|
-
|
39
|
+
"Remove multiple trailing newline at the end of the file.",
|
40
40
|
:remove
|
41
41
|
)
|
42
42
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "set"
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "better_html/tree/tag"
|
4
|
+
require "active_support/core_ext/string/inflections"
|
5
5
|
|
6
6
|
module ERBLint
|
7
7
|
module Linters
|
@@ -13,37 +13,37 @@ module ERBLint
|
|
13
13
|
MissingCorrector = Class.new(StandardError)
|
14
14
|
MissingI18nLoadPath = Class.new(StandardError)
|
15
15
|
|
16
|
-
ALLOWED_CORRECTORS =
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
&
|
24
|
-
&
|
25
|
-
&
|
26
|
-
&
|
27
|
-
&
|
28
|
-
&
|
29
|
-
&
|
30
|
-
&
|
31
|
-
&
|
32
|
-
&
|
33
|
-
&
|
34
|
-
&
|
35
|
-
&
|
36
|
-
&
|
37
|
-
&
|
38
|
-
&
|
39
|
-
&
|
40
|
-
&
|
41
|
-
&
|
42
|
-
)
|
16
|
+
ALLOWED_CORRECTORS = ["I18nCorrector", "RuboCop::Corrector::I18n::HardCodedString"]
|
17
|
+
|
18
|
+
NON_TEXT_TAGS = Set.new(["script", "style", "xmp", "iframe", "noembed", "noframes", "listing"])
|
19
|
+
TEXT_NOT_ALLOWED = Set.new([
|
20
|
+
" ",
|
21
|
+
"&",
|
22
|
+
"<",
|
23
|
+
">",
|
24
|
+
""",
|
25
|
+
"©",
|
26
|
+
"®",
|
27
|
+
"™",
|
28
|
+
"…",
|
29
|
+
"—",
|
30
|
+
"•",
|
31
|
+
"“",
|
32
|
+
"”",
|
33
|
+
"‘",
|
34
|
+
"’",
|
35
|
+
"←",
|
36
|
+
"→",
|
37
|
+
"↓",
|
38
|
+
"↑",
|
39
|
+
" ",
|
40
|
+
" ",
|
41
|
+
" ",
|
42
|
+
])
|
43
43
|
|
44
44
|
class ConfigSchema < LinterConfig
|
45
45
|
property :corrector, accepts: Hash, required: false, default: -> { {} }
|
46
|
-
property :i18n_load_path, accepts: String, required: false, default:
|
46
|
+
property :i18n_load_path, accepts: String, required: false, default: ""
|
47
47
|
end
|
48
48
|
self.config_schema = ConfigSchema
|
49
49
|
|
@@ -85,7 +85,7 @@ module ERBLint
|
|
85
85
|
return unless string.strip.length > 1
|
86
86
|
node = ::RuboCop::AST::StrNode.new(:str, [string])
|
87
87
|
corrector = klass.new(node, processed_source.filename, corrector_i18n_load_path, offense.source_range)
|
88
|
-
corrector.autocorrect(tag_start:
|
88
|
+
corrector.autocorrect(tag_start: "<%= ", tag_end: " %>")
|
89
89
|
rescue MissingCorrector, MissingI18nLoadPath
|
90
90
|
nil
|
91
91
|
end
|
@@ -93,20 +93,20 @@ module ERBLint
|
|
93
93
|
private
|
94
94
|
|
95
95
|
def check_string?(str)
|
96
|
-
string = str.gsub(/\s*/,
|
97
|
-
string.length > 1 && !
|
96
|
+
string = str.gsub(/\s*/, "")
|
97
|
+
string.length > 1 && !TEXT_NOT_ALLOWED.include?(string)
|
98
98
|
end
|
99
99
|
|
100
100
|
def load_corrector
|
101
|
-
corrector_name = @config[
|
101
|
+
corrector_name = @config["corrector"].fetch("name") { raise MissingCorrector }
|
102
102
|
raise ForbiddenCorrector unless ALLOWED_CORRECTORS.include?(corrector_name)
|
103
|
-
require @config[
|
103
|
+
require @config["corrector"].fetch("path") { raise MissingCorrector }
|
104
104
|
|
105
105
|
corrector_name.safe_constantize
|
106
106
|
end
|
107
107
|
|
108
108
|
def corrector_i18n_load_path
|
109
|
-
@config[
|
109
|
+
@config["corrector"].fetch("i18n_load_path") { raise MissingI18nLoadPath }
|
110
110
|
end
|
111
111
|
|
112
112
|
def non_text_tag?(processed_source, text_node)
|
@@ -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,7 @@ 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
|
source = code_node.loc.source
|
26
26
|
|
27
27
|
ruby_node =
|
@@ -63,10 +63,10 @@ module ERBLint
|
|
63
63
|
return unless (0..2).cover?(argument_nodes.size)
|
64
64
|
|
65
65
|
script_content = unless argument_nodes.first&.type?(:hash)
|
66
|
-
Utils::RubyToERB.ruby_to_erb(argument_nodes.first,
|
66
|
+
Utils::RubyToERB.ruby_to_erb(argument_nodes.first, "==")
|
67
67
|
end
|
68
68
|
arguments = if argument_nodes.last&.type?(:hash)
|
69
|
-
|
69
|
+
" " + Utils::RubyToERB.html_options_to_tag_attributes(argument_nodes.last)
|
70
70
|
end
|
71
71
|
|
72
72
|
return if end_node && script_content
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "better_html"
|
4
|
+
require "better_html/tree/tag"
|
5
5
|
|
6
6
|
module ERBLint
|
7
7
|
module Linters
|
@@ -13,7 +13,6 @@ module ERBLint
|
|
13
13
|
"date",
|
14
14
|
"datetime-local",
|
15
15
|
"email",
|
16
|
-
"hidden",
|
17
16
|
"month",
|
18
17
|
"number",
|
19
18
|
"password",
|
@@ -33,7 +32,6 @@ module ERBLint
|
|
33
32
|
:text_field_tag,
|
34
33
|
:utf8_enforcer_tag,
|
35
34
|
:month_field_tag,
|
36
|
-
:hidden_field_tag,
|
37
35
|
:number_field_tag,
|
38
36
|
:password_field_tag,
|
39
37
|
:search_field_tag,
|
@@ -56,8 +54,8 @@ module ERBLint
|
|
56
54
|
parser.nodes_with_type(:tag).each do |tag_node|
|
57
55
|
tag = BetterHtml::Tree::Tag.from_node(tag_node)
|
58
56
|
|
59
|
-
autocomplete_attribute = tag.attributes[
|
60
|
-
type_attribute = tag.attributes[
|
57
|
+
autocomplete_attribute = tag.attributes["autocomplete"]
|
58
|
+
type_attribute = tag.attributes["type"]
|
61
59
|
|
62
60
|
next if !html_input_tag?(tag) || autocomplete_present?(autocomplete_attribute)
|
63
61
|
next unless html_type_requires_autocomplete_attribute?(type_attribute)
|
@@ -76,7 +74,7 @@ module ERBLint
|
|
76
74
|
end
|
77
75
|
|
78
76
|
def html_input_tag?(tag)
|
79
|
-
!tag.closing? && tag.name ==
|
77
|
+
!tag.closing? && tag.name == "input"
|
80
78
|
end
|
81
79
|
|
82
80
|
def html_type_requires_autocomplete_attribute?(type_attribute)
|
@@ -110,7 +108,7 @@ module ERBLint
|
|
110
108
|
end
|
111
109
|
|
112
110
|
def code_comment?(indicator_node)
|
113
|
-
indicator_node&.loc&.source ==
|
111
|
+
indicator_node&.loc&.source == "#"
|
114
112
|
end
|
115
113
|
|
116
114
|
def extract_ruby_node(source)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "better_html"
|
4
|
+
require "better_html/tree/tag"
|
5
5
|
|
6
6
|
module ERBLint
|
7
7
|
module Linters
|
@@ -22,7 +22,7 @@ module ERBLint
|
|
22
22
|
def find_html_script_tags(parser)
|
23
23
|
parser.nodes_with_type(:tag).each do |tag_node|
|
24
24
|
tag = BetterHtml::Tree::Tag.from_node(tag_node)
|
25
|
-
nonce_attribute = tag.attributes[
|
25
|
+
nonce_attribute = tag.attributes["nonce"]
|
26
26
|
|
27
27
|
next if !html_javascript_tag?(tag) || nonce_present?(nonce_attribute)
|
28
28
|
|
@@ -40,16 +40,16 @@ module ERBLint
|
|
40
40
|
|
41
41
|
def html_javascript_tag?(tag)
|
42
42
|
!tag.closing? &&
|
43
|
-
(tag.name ==
|
43
|
+
(tag.name == "script" && !html_javascript_type_attribute?(tag))
|
44
44
|
end
|
45
45
|
|
46
46
|
def html_javascript_type_attribute?(tag)
|
47
|
-
type_attribute = tag.attributes[
|
47
|
+
type_attribute = tag.attributes["type"]
|
48
48
|
|
49
49
|
type_attribute &&
|
50
50
|
type_attribute.value_node.present? &&
|
51
|
-
type_attribute.value_node.to_a[1] !=
|
52
|
-
type_attribute.value_node.to_a[1] !=
|
51
|
+
type_attribute.value_node.to_a[1] != "text/javascript" &&
|
52
|
+
type_attribute.value_node.to_a[1] != "application/javascript"
|
53
53
|
end
|
54
54
|
|
55
55
|
def find_rails_helper_script_tags(parser)
|
@@ -79,7 +79,7 @@ module ERBLint
|
|
79
79
|
end
|
80
80
|
|
81
81
|
def code_comment?(indicator_node)
|
82
|
-
indicator_node&.loc&.source ==
|
82
|
+
indicator_node&.loc&.source == "#"
|
83
83
|
end
|
84
84
|
|
85
85
|
def extract_ruby_node(source)
|
@@ -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)
|
@@ -68,13 +68,13 @@ module ERBLint
|
|
68
68
|
|
69
69
|
def inspect_content(processed_source, erb_node)
|
70
70
|
indicator, _, code_node, = *erb_node
|
71
|
-
return if indicator&.children&.first ==
|
71
|
+
return if indicator&.children&.first == "#"
|
72
72
|
|
73
73
|
original_source = code_node.loc.source
|
74
|
-
trimmed_source = original_source.sub(BLOCK_EXPR,
|
74
|
+
trimmed_source = original_source.sub(BLOCK_EXPR, "").sub(SUFFIX_EXPR, "")
|
75
75
|
alignment_column = code_node.loc.column
|
76
76
|
offset = code_node.loc.begin_pos - alignment_column
|
77
|
-
aligned_source = "#{
|
77
|
+
aligned_source = "#{" " * alignment_column}#{trimmed_source}"
|
78
78
|
|
79
79
|
source = rubocop_processed_source(aligned_source, processed_source.filename)
|
80
80
|
return unless source.valid_syntax?
|
@@ -156,10 +156,10 @@ module ERBLint
|
|
156
156
|
end
|
157
157
|
|
158
158
|
def config_from_hash(hash)
|
159
|
-
inherit_from = hash&.delete(
|
159
|
+
inherit_from = hash&.delete("inherit_from")
|
160
160
|
resolve_inheritance(hash, inherit_from)
|
161
161
|
|
162
|
-
tempfile_from(
|
162
|
+
tempfile_from(".erblint-rubocop", hash.to_yaml) do |tempfile|
|
163
163
|
::RuboCop::ConfigLoader.load_file(tempfile.path)
|
164
164
|
end
|
165
165
|
end
|
@@ -174,7 +174,7 @@ module ERBLint
|
|
174
174
|
end
|
175
175
|
|
176
176
|
def base_configs(inherit_from)
|
177
|
-
regex = URI::DEFAULT_PARSER.make_regexp(
|
177
|
+
regex = URI::DEFAULT_PARSER.make_regexp(["http", "https"])
|
178
178
|
configs = Array(inherit_from).compact.map do |base_name|
|
179
179
|
if base_name =~ /\A#{regex}\z/
|
180
180
|
::RuboCop::ConfigLoader.load_file(::RuboCop::RemoteConfig.new(base_name, Dir.pwd))
|
@@ -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,7 +32,7 @@ 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
|
|
@@ -43,7 +41,7 @@ module ERBLint
|
|
43
41
|
add_offense(
|
44
42
|
end_solidus.loc,
|
45
43
|
"Tag `#{tag.name}` is a void element, it must end with `>` and not `/>`.",
|
46
|
-
|
44
|
+
""
|
47
45
|
)
|
48
46
|
end
|
49
47
|
end
|
@@ -15,7 +15,7 @@ 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
|
code = code_node.children.first
|
20
20
|
|
21
21
|
start_spaces = code.match(START_SPACES)&.captures&.first || ""
|
@@ -23,8 +23,8 @@ module ERBLint
|
|
23
23
|
add_offense(
|
24
24
|
code_node.loc.resize(start_spaces.size),
|
25
25
|
"Use 1 space after `<%#{indicator}#{ltrim&.loc&.source}` "\
|
26
|
-
"instead of #{start_spaces.size} space#{
|
27
|
-
|
26
|
+
"instead of #{start_spaces.size} space#{"s" if start_spaces.size > 1}.",
|
27
|
+
" "
|
28
28
|
)
|
29
29
|
elsif start_spaces.count("\n") > 1
|
30
30
|
lines = start_spaces.split("\n", -1)
|
@@ -41,8 +41,8 @@ module ERBLint
|
|
41
41
|
add_offense(
|
42
42
|
code_node.loc.end.adjust(begin_pos: -end_spaces.size),
|
43
43
|
"Use 1 space before `#{rtrim&.loc&.source}%>` "\
|
44
|
-
"instead of #{end_spaces.size} space#{
|
45
|
-
|
44
|
+
"instead of #{end_spaces.size} space#{"s" if start_spaces.size > 1}.",
|
45
|
+
" "
|
46
46
|
)
|
47
47
|
elsif end_spaces.count("\n") > 1
|
48
48
|
lines = end_spaces.split("\n", -1)
|
@@ -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
|
@@ -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/reporter.rb
CHANGED
@@ -9,7 +9,7 @@ module ERBLint
|
|
9
9
|
def format_offense(filename, offense)
|
10
10
|
<<~EOF
|
11
11
|
|
12
|
-
#{offense.message}#{Rainbow(
|
12
|
+
#{offense.message}#{Rainbow(" (not autocorrected)").red if autocorrect}
|
13
13
|
In file: #{filename}:#{offense.line_number}
|
14
14
|
EOF
|
15
15
|
end
|
data/lib/erb_lint/runner.rb
CHANGED
@@ -8,7 +8,7 @@ module ERBLint
|
|
8
8
|
def initialize(file_loader, config)
|
9
9
|
@file_loader = file_loader
|
10
10
|
@config = config || RunnerConfig.default
|
11
|
-
raise ArgumentError,
|
11
|
+
raise ArgumentError, "expect `config` to be a RunnerConfig instance" unless @config.is_a?(RunnerConfig)
|
12
12
|
|
13
13
|
linter_classes = LinterRegistry.linters.select { |klass| @config.for_linter(klass).enabled? }
|
14
14
|
@linters = linter_classes.map do |linter_class|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "erb_lint/runner_config_resolver"
|
4
4
|
|
5
5
|
module ERBLint
|
6
6
|
class RunnerConfig
|
@@ -9,7 +9,7 @@ module ERBLint
|
|
9
9
|
def initialize(config = nil, file_loader = nil)
|
10
10
|
@config = (config || {}).dup.deep_stringify_keys
|
11
11
|
|
12
|
-
resolver.resolve_inheritance_from_gems(@config, @config.delete(
|
12
|
+
resolver.resolve_inheritance_from_gems(@config, @config.delete("inherit_gem"))
|
13
13
|
resolver.resolve_inheritance(@config, file_loader) if file_loader
|
14
14
|
@config.delete("inherit_from")
|
15
15
|
end
|
@@ -24,7 +24,7 @@ module ERBLint
|
|
24
24
|
elsif klass.is_a?(Class) && klass <= ERBLint::Linter
|
25
25
|
klass.simple_name
|
26
26
|
else
|
27
|
-
raise ArgumentError,
|
27
|
+
raise ArgumentError, "expected String or linter class"
|
28
28
|
end
|
29
29
|
linter_klass = LinterRegistry.find_by_name(klass_name)
|
30
30
|
raise Error, "#{klass_name}: linter not found (is it loaded?)" unless linter_klass
|
@@ -32,7 +32,7 @@ module ERBLint
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def global_exclude
|
35
|
-
@config[
|
35
|
+
@config["exclude"] || []
|
36
36
|
end
|
37
37
|
|
38
38
|
def merge(other_config)
|
@@ -75,13 +75,13 @@ module ERBLint
|
|
75
75
|
private
|
76
76
|
|
77
77
|
def linters_config
|
78
|
-
@config[
|
78
|
+
@config["linters"] || {}
|
79
79
|
end
|
80
80
|
|
81
81
|
def config_hash_for_linter(klass_name)
|
82
82
|
config_hash = linters_config[klass_name] || {}
|
83
|
-
config_hash[
|
84
|
-
config_hash[
|
83
|
+
config_hash["exclude"] ||= []
|
84
|
+
config_hash["exclude"].concat(global_exclude) if config_hash["exclude"].is_a?(Array)
|
85
85
|
config_hash
|
86
86
|
end
|
87
87
|
|
@@ -24,7 +24,7 @@
|
|
24
24
|
module ERBLint
|
25
25
|
class RunnerConfigResolver
|
26
26
|
def resolve_inheritance(hash, file_loader)
|
27
|
-
inherited_files = Array(hash[
|
27
|
+
inherited_files = Array(hash["inherit_from"])
|
28
28
|
base_configs(file_loader, inherited_files).reverse_each do |base_config|
|
29
29
|
base_config.each do |k, v|
|
30
30
|
next unless v.is_a?(Hash)
|
@@ -36,12 +36,12 @@ module ERBLint
|
|
36
36
|
|
37
37
|
def resolve_inheritance_from_gems(hash, gems)
|
38
38
|
(gems || {}).each_pair do |gem_name, config_path|
|
39
|
-
raise(ArgumentError, "can't inherit configuration from the erb-lint gem") if gem_name ==
|
39
|
+
raise(ArgumentError, "can't inherit configuration from the erb-lint gem") if gem_name == "erb-lint"
|
40
40
|
|
41
|
-
hash[
|
41
|
+
hash["inherit_from"] = Array(hash["inherit_from"])
|
42
42
|
Array(config_path).reverse_each do |path|
|
43
43
|
# Put gem configuration first so local configuration overrides it.
|
44
|
-
hash[
|
44
|
+
hash["inherit_from"].unshift(gem_config_path(gem_name, path))
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
data/lib/erb_lint/stats.rb
CHANGED
@@ -2,12 +2,12 @@
|
|
2
2
|
module ERBLint
|
3
3
|
class Stats
|
4
4
|
attr_accessor :ignored,
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
:found,
|
6
|
+
:corrected,
|
7
|
+
:exceptions,
|
8
|
+
:linters,
|
9
|
+
:files,
|
10
|
+
:processed_files
|
11
11
|
|
12
12
|
def initialize(
|
13
13
|
ignored: 0,
|
@@ -9,10 +9,10 @@ module ERBLint
|
|
9
9
|
def html_options_to_tag_attributes(hash_node)
|
10
10
|
hash_node.children.map do |pair_node|
|
11
11
|
key_node, value_node = *pair_node
|
12
|
-
key = ruby_to_erb(key_node,
|
13
|
-
value = ruby_to_erb(value_node,
|
14
|
-
[key, "\"#{value}\""].join(
|
15
|
-
end.join(
|
12
|
+
key = ruby_to_erb(key_node, "=") { |s| s.tr("_", "-") }
|
13
|
+
value = ruby_to_erb(value_node, "=") { |s| escape_quote(s) }
|
14
|
+
[key, "\"#{value}\""].join("=")
|
15
|
+
end.join(" ")
|
16
16
|
end
|
17
17
|
|
18
18
|
def ruby_to_erb(node, indicator = nil, &block)
|
@@ -43,7 +43,7 @@ module ERBLint
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def escape_quote(str)
|
46
|
-
str.gsub('"',
|
46
|
+
str.gsub('"', """)
|
47
47
|
end
|
48
48
|
end
|
49
49
|
end
|
@@ -3,10 +3,10 @@
|
|
3
3
|
module ERBLint
|
4
4
|
module Utils
|
5
5
|
module SeverityLevels
|
6
|
-
SEVERITY_NAMES =
|
6
|
+
SEVERITY_NAMES = [:info, :refactor, :convention, :warning, :error, :fatal].freeze
|
7
7
|
|
8
8
|
SEVERITY_CODE_TABLE = { I: :info, R: :refactor, C: :convention,
|
9
|
-
W: :warning, E: :error, F: :fatal }.freeze
|
9
|
+
W: :warning, E: :error, F: :fatal, }.freeze
|
10
10
|
|
11
11
|
def severity_level_for_name(name)
|
12
12
|
SEVERITY_NAMES.index(name || :error) + 1
|
data/lib/erb_lint/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: erb_lint
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Chan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-07-
|
11
|
+
date: 2021-07-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: better_html
|