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