erb_lint 0.0.9 → 0.0.11
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 +9 -0
- data/lib/erb_lint.rb +3 -0
- data/lib/erb_lint/cli.rb +198 -0
- data/lib/erb_lint/linter.rb +21 -4
- data/lib/erb_lint/linter_config.rb +60 -0
- data/lib/erb_lint/linter_registry.rb +4 -0
- data/lib/erb_lint/linters/deprecated_classes.rb +62 -41
- data/lib/erb_lint/linters/erb_safety.rb +7 -1
- data/lib/erb_lint/linters/final_newline.rb +6 -1
- data/lib/erb_lint/linters/rubocop.rb +63 -18
- data/lib/erb_lint/runner.rb +7 -33
- data/lib/erb_lint/runner_config.rb +55 -0
- data/lib/erb_lint/version.rb +5 -0
- metadata +54 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb68fe352cf7b67dba7ec568ad6d40bc87e740f5
|
4
|
+
data.tar.gz: 6cafdb08d656bcfe14666a7e00b9f144e741b62a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1fb5769c9cd386762eae39367c60ce941719017bf20eb911d478ca17e5d8dd5f923226408369a6b870707ee15eb5f3f5c8bc5b0133535346501190e998ca75da
|
7
|
+
data.tar.gz: fcd8c9009447122ded1b08a0855bd5a60a755a71fc341a03b84995d7cb7945c1a28338e5bb64f5368d021c2ee055ded8e2a1b906dd974ec11d6cc1f159f352ca
|
data/exe/erblint
ADDED
data/lib/erb_lint.rb
CHANGED
data/lib/erb_lint/cli.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erb_lint'
|
4
|
+
require 'active_support'
|
5
|
+
require 'active_support/inflector'
|
6
|
+
require 'optparse'
|
7
|
+
require 'psych'
|
8
|
+
require 'yaml'
|
9
|
+
require 'colorize'
|
10
|
+
|
11
|
+
module ERBLint
|
12
|
+
class CLI
|
13
|
+
DEFAULT_CONFIG_FILENAME = '.erb-lint.yml'
|
14
|
+
DEFAULT_LINT_ALL_GLOB = "**/*.html{+*,}.erb"
|
15
|
+
|
16
|
+
class ExitWithFailure < RuntimeError; end
|
17
|
+
class ExitWithSuccess < RuntimeError; end
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@options = {}
|
21
|
+
@config = nil
|
22
|
+
@files = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def run(args = ARGV)
|
26
|
+
load_options(args)
|
27
|
+
@files = args.dup
|
28
|
+
|
29
|
+
if lint_files.empty?
|
30
|
+
success!("no files given...\n#{option_parser}")
|
31
|
+
end
|
32
|
+
|
33
|
+
load_config
|
34
|
+
ensure_files_exist(lint_files)
|
35
|
+
|
36
|
+
puts "Linting #{lint_files.size} files with #{enabled_linter_classes.size} linters..."
|
37
|
+
puts
|
38
|
+
|
39
|
+
errors_found = false
|
40
|
+
runner_config = @config.merge(runner_config_override)
|
41
|
+
runner = ERBLint::Runner.new(file_loader, runner_config)
|
42
|
+
lint_files.each do |filename|
|
43
|
+
lint_result = runner.run(filename, File.read(filename))
|
44
|
+
errors = lint_result.map { |r| r[:errors] }.flatten
|
45
|
+
errors.each do |lint_error|
|
46
|
+
puts <<~EOF
|
47
|
+
#{lint_error[:message]}
|
48
|
+
In file: #{relative_filename(filename)}:#{lint_error[:line]}
|
49
|
+
|
50
|
+
EOF
|
51
|
+
errors_found = true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if errors_found
|
56
|
+
warn "Errors were found in ERB files".red
|
57
|
+
else
|
58
|
+
puts "No errors were found in ERB files".green
|
59
|
+
end
|
60
|
+
|
61
|
+
!errors_found
|
62
|
+
rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, ExitWithFailure => e
|
63
|
+
warn e.message.red
|
64
|
+
false
|
65
|
+
rescue ExitWithSuccess => e
|
66
|
+
puts e.message
|
67
|
+
true
|
68
|
+
rescue => e
|
69
|
+
warn "#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}".red
|
70
|
+
false
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def config_filename
|
76
|
+
@config_filename ||= @options[:config] || DEFAULT_CONFIG_FILENAME
|
77
|
+
end
|
78
|
+
|
79
|
+
def load_config
|
80
|
+
if File.exist?(config_filename)
|
81
|
+
@config = RunnerConfig.new(file_loader.yaml(config_filename))
|
82
|
+
else
|
83
|
+
warn "#{config_filename} not found: using default config".yellow
|
84
|
+
@config = RunnerConfig.default
|
85
|
+
end
|
86
|
+
rescue Psych::SyntaxError => e
|
87
|
+
failure!("error parsing config: #{e.message}")
|
88
|
+
end
|
89
|
+
|
90
|
+
def file_loader
|
91
|
+
@file_loader ||= ERBLint::FileLoader.new(Dir.pwd)
|
92
|
+
end
|
93
|
+
|
94
|
+
def load_options(args)
|
95
|
+
option_parser.parse!(args)
|
96
|
+
end
|
97
|
+
|
98
|
+
def lint_files
|
99
|
+
if @options[:lint_all]
|
100
|
+
pattern = File.expand_path(DEFAULT_LINT_ALL_GLOB, Dir.pwd)
|
101
|
+
Dir[pattern]
|
102
|
+
else
|
103
|
+
@files.map { |f| f.include?('*') ? Dir[f] : f }.flatten.map { |f| File.expand_path(f, Dir.pwd) }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def failure!(msg)
|
108
|
+
raise ExitWithFailure, msg
|
109
|
+
end
|
110
|
+
|
111
|
+
def success!(msg)
|
112
|
+
raise ExitWithSuccess, msg
|
113
|
+
end
|
114
|
+
|
115
|
+
def ensure_files_exist(files)
|
116
|
+
files.each do |filename|
|
117
|
+
unless File.exist?(filename)
|
118
|
+
failure!("#{filename}: does not exist")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def known_linter_names
|
124
|
+
@known_linter_names ||= ERBLint::LinterRegistry.linters
|
125
|
+
.map(&:simple_name)
|
126
|
+
.map(&:underscore)
|
127
|
+
end
|
128
|
+
|
129
|
+
def enabled_linter_names
|
130
|
+
@enabled_linter_names ||=
|
131
|
+
@options[:enabled_linters] ||
|
132
|
+
known_linter_names.select { |klass| @config.for_linter(klass).enabled? }
|
133
|
+
end
|
134
|
+
|
135
|
+
def enabled_linter_classes
|
136
|
+
@enabled_linter_classes ||= ERBLint::LinterRegistry.linters
|
137
|
+
.select { |klass| enabled_linter_names.include?(klass.simple_name.underscore) }
|
138
|
+
end
|
139
|
+
|
140
|
+
def relative_filename(filename)
|
141
|
+
filename.sub("#{File.expand_path('.', Dir.pwd)}/", '')
|
142
|
+
end
|
143
|
+
|
144
|
+
def runner_config_override
|
145
|
+
if @options[:enabled_linters].present?
|
146
|
+
RunnerConfig.new(
|
147
|
+
linters: {}.tap do |linters|
|
148
|
+
ERBLint::LinterRegistry.linters.map do |klass|
|
149
|
+
linters[klass.simple_name] = { 'enabled' => enabled_linter_classes.include?(klass) }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
)
|
153
|
+
else
|
154
|
+
RunnerConfig.new
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def option_parser
|
159
|
+
OptionParser.new do |opts|
|
160
|
+
opts.banner = "Usage: erblint [options] [file1, file2, ...]"
|
161
|
+
|
162
|
+
opts.on("--config FILENAME", "Config file [default: #{DEFAULT_CONFIG_FILENAME}]") do |config|
|
163
|
+
if File.exist?(config)
|
164
|
+
@options[:config] = config
|
165
|
+
else
|
166
|
+
failure!("#{config}: does not exist")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
opts.on("--lint-all", "Lint all files matching #{DEFAULT_LINT_ALL_GLOB}") do |config|
|
171
|
+
@options[:lint_all] = config
|
172
|
+
end
|
173
|
+
|
174
|
+
opts.on("--enable-all-linters", "Enable all known linters") do
|
175
|
+
@options[:enabled_linters] = known_linter_names
|
176
|
+
end
|
177
|
+
|
178
|
+
opts.on("--enable-linters LINTER[,LINTER,...]", Array,
|
179
|
+
"Only use specified linter", "Known linters are: #{known_linter_names.join(', ')}") do |linters|
|
180
|
+
linters.each do |linter|
|
181
|
+
unless known_linter_names.include?(linter)
|
182
|
+
failure!("#{linter}: not a valid linter name (#{known_linter_names.join(', ')})")
|
183
|
+
end
|
184
|
+
end
|
185
|
+
@options[:enabled_linters] = linters
|
186
|
+
end
|
187
|
+
|
188
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
189
|
+
success!(opts)
|
190
|
+
end
|
191
|
+
|
192
|
+
opts.on_tail("--version", "Show version") do
|
193
|
+
success!(ERBLint::VERSION)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
data/lib/erb_lint/linter.rb
CHANGED
@@ -5,6 +5,7 @@ module ERBLint
|
|
5
5
|
class Linter
|
6
6
|
class << self
|
7
7
|
attr_accessor :simple_name
|
8
|
+
attr_accessor :config_schema
|
8
9
|
|
9
10
|
# When defining a Linter class, define its simple name as well. This
|
10
11
|
# assumes that the module hierarchy of every linter starts with
|
@@ -13,15 +14,31 @@ module ERBLint
|
|
13
14
|
# `ERBLint::Linters::Foo.simple_name` #=> "Foo"
|
14
15
|
# `ERBLint::Linters::Compass::Bar.simple_name` #=> "Compass::Bar"
|
15
16
|
def inherited(linter)
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
linter.simple_name = if linter.name.start_with?('ERBLint::Linters::')
|
18
|
+
name_parts = linter.name.split('::')
|
19
|
+
name_parts[2..-1].join('::')
|
20
|
+
else
|
21
|
+
linter.name
|
22
|
+
end
|
23
|
+
|
24
|
+
linter.config_schema = LinterConfig
|
19
25
|
end
|
20
26
|
end
|
21
27
|
|
22
28
|
# Must be implemented by the concrete inheriting class.
|
23
|
-
def initialize(file_loader,
|
29
|
+
def initialize(file_loader, config)
|
24
30
|
@file_loader = file_loader
|
31
|
+
@config = config
|
32
|
+
raise ArgumentError, "expect `config` to be #{self.class.config_schema} instance, "\
|
33
|
+
"not #{config.class}" unless config.is_a?(self.class.config_schema)
|
34
|
+
end
|
35
|
+
|
36
|
+
def enabled?
|
37
|
+
@config.enabled?
|
38
|
+
end
|
39
|
+
|
40
|
+
def excludes_file?(filename)
|
41
|
+
@config.excludes_file?(filename)
|
25
42
|
end
|
26
43
|
|
27
44
|
def lint_file(file_content)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'smart_properties'
|
5
|
+
|
6
|
+
module ERBLint
|
7
|
+
class LinterConfig
|
8
|
+
include SmartProperties
|
9
|
+
|
10
|
+
class Error < StandardError; end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def array_of?(klass)
|
14
|
+
lambda { |value| value.is_a?(Array) && value.all? { |s| s.is_a?(klass) } }
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_array_of(klass)
|
18
|
+
lambda { |entries| entries.map { |entry| klass.new(entry) } }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
property :enabled, accepts: [true, false], default: false, reader: :enabled?
|
23
|
+
property :exclude, accepts: array_of?(String), default: []
|
24
|
+
|
25
|
+
def initialize(config = {})
|
26
|
+
config = config.dup.deep_stringify_keys
|
27
|
+
allowed_keys = self.class.properties.keys.map(&:to_s)
|
28
|
+
given_keys = config.keys
|
29
|
+
if (extra_keys = given_keys - allowed_keys).any?
|
30
|
+
raise Error, "Given key is not allowed: #{extra_keys.join(', ')}"
|
31
|
+
end
|
32
|
+
super(config)
|
33
|
+
rescue SmartProperties::InitializationError => e
|
34
|
+
raise Error, "The following properties are required to be set: #{e.properties}"
|
35
|
+
rescue SmartProperties::InvalidValueError => e
|
36
|
+
raise Error, e.message
|
37
|
+
end
|
38
|
+
|
39
|
+
def [](name)
|
40
|
+
unless self.class.properties.key?(name)
|
41
|
+
raise Error, "No such property: #{name}"
|
42
|
+
end
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_hash
|
47
|
+
{}.tap do |hash|
|
48
|
+
self.class.properties.to_hash.each_key do |key|
|
49
|
+
hash[key.to_s] = self[key]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def excludes_file?(filename)
|
55
|
+
exclude.any? do |path|
|
56
|
+
File.fnmatch?(path, filename)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -13,6 +13,10 @@ module ERBLint
|
|
13
13
|
@linters << linter_class
|
14
14
|
end
|
15
15
|
|
16
|
+
def find_by_name(name)
|
17
|
+
linters.detect { |linter| linter.simple_name == name }
|
18
|
+
end
|
19
|
+
|
16
20
|
def load_custom_linters(directory = CUSTOM_LINTERS_DIR)
|
17
21
|
ruby_files = Dir.glob(File.expand_path(File.join(directory, '**', '*.rb')))
|
18
22
|
ruby_files.each { |file| require file }
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'better_html'
|
3
|
+
require 'better_html/parser'
|
4
4
|
|
5
5
|
module ERBLint
|
6
6
|
module Linters
|
@@ -8,67 +8,79 @@ module ERBLint
|
|
8
8
|
class DeprecatedClasses < Linter
|
9
9
|
include LinterRegistry
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
class RuleSet
|
12
|
+
include SmartProperties
|
13
|
+
property :suggestion, accepts: String, default: ''
|
14
|
+
property :deprecated, accepts: LinterConfig.array_of?(String), default: []
|
15
|
+
end
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
end
|
24
|
-
@deprecated_ruleset.freeze
|
17
|
+
class ConfigSchema < LinterConfig
|
18
|
+
property :rule_set,
|
19
|
+
default: [],
|
20
|
+
accepts: array_of?(RuleSet),
|
21
|
+
converts: to_array_of(RuleSet)
|
22
|
+
property :addendum, accepts: String
|
23
|
+
end
|
24
|
+
self.config_schema = ConfigSchema
|
25
25
|
|
26
|
-
|
26
|
+
def initialize(file_loader, config)
|
27
|
+
super
|
28
|
+
@addendum = @config.addendum
|
27
29
|
end
|
28
30
|
|
29
31
|
def lint_file(file_content)
|
30
32
|
errors = []
|
31
|
-
|
32
|
-
|
33
|
-
errors.push(*generate_errors(class_name, line))
|
33
|
+
parser = build_parser(file_content)
|
34
|
+
class_name_with_loc(parser).each do |class_name, loc|
|
35
|
+
errors.push(*generate_errors(class_name, loc.line))
|
34
36
|
end
|
35
|
-
|
36
|
-
errors.push(*lint_file(
|
37
|
+
text_tags_content(parser).each do |content|
|
38
|
+
errors.push(*lint_file(content))
|
37
39
|
end
|
38
40
|
errors
|
39
41
|
end
|
40
42
|
|
41
43
|
private
|
42
44
|
|
43
|
-
def
|
44
|
-
BetterHtml::
|
45
|
+
def build_parser(file_content)
|
46
|
+
BetterHtml::Parser.new(file_content, template_language: :html)
|
45
47
|
end
|
46
48
|
|
47
|
-
def
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
def class_name_with_loc(parser)
|
50
|
+
Enumerator.new do |yielder|
|
51
|
+
tags(parser).each do |tag|
|
52
|
+
class_value = tag.attributes['class']&.value
|
53
|
+
next unless class_value
|
54
|
+
class_value.split(' ').each do |class_name|
|
55
|
+
yielder.yield(class_name, tag.loc)
|
56
|
+
end
|
53
57
|
end
|
54
58
|
end
|
55
59
|
end
|
56
60
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
61
|
+
def text_tags_content(parser)
|
62
|
+
Enumerator.new do |yielder|
|
63
|
+
script_tags(parser)
|
64
|
+
.select { |tag| tag.attributes['type']&.value == 'text/html' }
|
65
|
+
.each do |tag|
|
66
|
+
index = parser.ast.to_a.find_index(tag.node)
|
67
|
+
next_node = parser.ast.to_a[index + 1]
|
68
|
+
|
69
|
+
yielder.yield(next_node.loc.source) if next_node.type == :text
|
70
|
+
end
|
60
71
|
end
|
61
72
|
end
|
62
73
|
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
next unless type
|
67
|
-
next unless type.value_without_quotes == 'text/html'
|
68
|
-
next_node = iterator.nodes[index + 1]
|
74
|
+
def script_tags(parser)
|
75
|
+
tags(parser).select { |tag| tag.name == 'script' }
|
76
|
+
end
|
69
77
|
|
70
|
-
|
71
|
-
|
78
|
+
def tags(parser)
|
79
|
+
tag_nodes(parser).map { |tag_node| BetterHtml::Tree::Tag.from_node(tag_node) }
|
80
|
+
end
|
81
|
+
|
82
|
+
def tag_nodes(parser)
|
83
|
+
parser.nodes_with_type(:tag)
|
72
84
|
end
|
73
85
|
|
74
86
|
def generate_errors(class_name, line_number)
|
@@ -83,8 +95,17 @@ module ERBLint
|
|
83
95
|
end
|
84
96
|
|
85
97
|
def violated_rules(class_name)
|
86
|
-
|
87
|
-
|
98
|
+
[].tap do |result|
|
99
|
+
@config.rule_set.each do |rule|
|
100
|
+
rule.deprecated.each do |deprecated|
|
101
|
+
next unless /\A#{deprecated}\z/ =~ class_name
|
102
|
+
|
103
|
+
result << {
|
104
|
+
suggestion: rule.suggestion,
|
105
|
+
class_expr: deprecated,
|
106
|
+
}
|
107
|
+
end
|
108
|
+
end
|
88
109
|
end
|
89
110
|
end
|
90
111
|
end
|
@@ -10,9 +10,15 @@ module ERBLint
|
|
10
10
|
include LinterRegistry
|
11
11
|
include BetterHtml::TestHelper::SafeErbTester
|
12
12
|
|
13
|
+
class ConfigSchema < LinterConfig
|
14
|
+
property :better_html_config, accepts: String
|
15
|
+
end
|
16
|
+
|
17
|
+
self.config_schema = ConfigSchema
|
18
|
+
|
13
19
|
def initialize(file_loader, config)
|
14
20
|
super
|
15
|
-
@config_filename = config
|
21
|
+
@config_filename = @config.better_html_config
|
16
22
|
end
|
17
23
|
|
18
24
|
def lint_file(file_content)
|
@@ -6,9 +6,14 @@ module ERBLint
|
|
6
6
|
class FinalNewline < Linter
|
7
7
|
include LinterRegistry
|
8
8
|
|
9
|
+
class ConfigSchema < LinterConfig
|
10
|
+
property :present, accepts: [true, false], default: true, reader: :present?
|
11
|
+
end
|
12
|
+
self.config_schema = ConfigSchema
|
13
|
+
|
9
14
|
def initialize(file_loader, config)
|
10
15
|
super
|
11
|
-
@new_lines_should_be_present = config
|
16
|
+
@new_lines_should_be_present = @config.present?
|
12
17
|
end
|
13
18
|
|
14
19
|
protected
|
@@ -10,27 +10,34 @@ module ERBLint
|
|
10
10
|
class Rubocop < Linter
|
11
11
|
include LinterRegistry
|
12
12
|
|
13
|
+
class ConfigSchema < LinterConfig
|
14
|
+
property :only, accepts: array_of?(String)
|
15
|
+
property :rubocop_config, accepts: Hash
|
16
|
+
end
|
17
|
+
|
18
|
+
self.config_schema = ConfigSchema
|
19
|
+
|
13
20
|
# copied from Rails: action_view/template/handlers/erb/erubi.rb
|
14
21
|
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
15
22
|
|
16
|
-
def initialize(file_loader,
|
23
|
+
def initialize(file_loader, config)
|
17
24
|
super
|
18
|
-
@
|
19
|
-
|
20
|
-
|
21
|
-
@config = RuboCop::ConfigLoader.merge_with_default(custom_config, '')
|
22
|
-
end
|
25
|
+
@only_cops = @config.only
|
26
|
+
custom_config = config_from_hash(@config.rubocop_config)
|
27
|
+
@rubocop_config = RuboCop::ConfigLoader.merge_with_default(custom_config, '')
|
23
28
|
end
|
24
29
|
|
25
30
|
def lint_file(file_content)
|
26
31
|
errors = []
|
27
|
-
|
28
|
-
erb.
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
+
parser = BetterHtml::Parser.new(file_content, template_language: :html)
|
33
|
+
parser.ast.descendants(:erb).each do |erb_node|
|
34
|
+
_, _, code_node, = *erb_node
|
35
|
+
code = code_node.loc.source.sub(/\A[[:blank:]]*/, '')
|
36
|
+
code = "#{' ' * erb_node.loc.column}#{code}"
|
37
|
+
code = code.sub(BLOCK_EXPR, '')
|
38
|
+
offenses = inspect_content(code)
|
32
39
|
offenses&.each do |offense|
|
33
|
-
errors << format_error(
|
40
|
+
errors << format_error(code_node, offense)
|
34
41
|
end
|
35
42
|
end
|
36
43
|
errors
|
@@ -57,23 +64,61 @@ module ERBLint
|
|
57
64
|
def processed_source(content)
|
58
65
|
RuboCop::ProcessedSource.new(
|
59
66
|
content,
|
60
|
-
@
|
67
|
+
@rubocop_config.target_ruby_version,
|
61
68
|
'(erb)'
|
62
69
|
)
|
63
70
|
end
|
64
71
|
|
65
72
|
def team
|
66
|
-
|
67
|
-
|
68
|
-
|
73
|
+
cop_classes =
|
74
|
+
if @only_cops.present?
|
75
|
+
selected_cops = RuboCop::Cop::Cop.all.select { |cop| cop.match?(@only_cops) }
|
76
|
+
RuboCop::Cop::Registry.new(selected_cops)
|
77
|
+
elsif @rubocop_config['Rails']['Enabled']
|
78
|
+
RuboCop::Cop::Registry.new(RuboCop::Cop::Cop.all)
|
79
|
+
else
|
80
|
+
RuboCop::Cop::Cop.non_rails
|
81
|
+
end
|
82
|
+
RuboCop::Cop::Team.new(cop_classes, @rubocop_config, extra_details: true, display_cop_names: true)
|
69
83
|
end
|
70
84
|
|
71
|
-
def format_error(
|
85
|
+
def format_error(code_node, offense)
|
72
86
|
{
|
73
|
-
line:
|
87
|
+
line: code_node.loc.line + offense.line - 1,
|
74
88
|
message: offense.message.strip
|
75
89
|
}
|
76
90
|
end
|
91
|
+
|
92
|
+
def config_from_hash(hash)
|
93
|
+
inherit_from = hash.delete('inherit_from')
|
94
|
+
resolve_inheritance(hash, inherit_from)
|
95
|
+
|
96
|
+
tempfile_from('.erblint-rubocop', hash.to_yaml) do |tempfile|
|
97
|
+
RuboCop::ConfigLoader.load_file(tempfile.path)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def resolve_inheritance(hash, inherit_from)
|
102
|
+
base_configs(inherit_from)
|
103
|
+
.reverse_each do |base_config|
|
104
|
+
base_config.each do |k, v|
|
105
|
+
hash[k] = hash.key?(k) ? RuboCop::ConfigLoader.merge(v, hash[k]) : v if v.is_a?(Hash)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def base_configs(inherit_from)
|
111
|
+
regex = URI::DEFAULT_PARSER.make_regexp(%w(http https))
|
112
|
+
configs = Array(inherit_from).compact.map do |base_name|
|
113
|
+
if base_name =~ /\A#{regex}\z/
|
114
|
+
RuboCop::ConfigLoader.load_file(RuboCop::RemoteConfig.new(base_name, Dir.pwd))
|
115
|
+
else
|
116
|
+
config_from_hash(@file_loader.yaml(base_name))
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
configs.compact
|
121
|
+
end
|
77
122
|
end
|
78
123
|
end
|
79
124
|
end
|
data/lib/erb_lint/runner.rb
CHANGED
@@ -3,20 +3,20 @@
|
|
3
3
|
module ERBLint
|
4
4
|
# Runs all enabled linters against an html.erb file.
|
5
5
|
class Runner
|
6
|
-
def initialize(file_loader, config
|
6
|
+
def initialize(file_loader, config)
|
7
7
|
@file_loader = file_loader
|
8
|
-
@config =
|
8
|
+
@config = config || RunnerConfig.default
|
9
|
+
raise ArgumentError, 'expect `config` to be a RunnerConfig instance' unless @config.is_a?(RunnerConfig)
|
9
10
|
|
10
11
|
LinterRegistry.load_custom_linters
|
11
|
-
|
12
|
-
@linters.map
|
13
|
-
|
14
|
-
linter_class.new(@file_loader, linter_config)
|
12
|
+
linter_classes = LinterRegistry.linters.select { |klass| @config.for_linter(klass).enabled? }
|
13
|
+
@linters = linter_classes.map do |linter_class|
|
14
|
+
linter_class.new(@file_loader, @config.for_linter(linter_class))
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
18
|
def run(filename, file_content)
|
19
|
-
linters_for_file = @linters.reject { |linter|
|
19
|
+
linters_for_file = @linters.reject { |linter| linter.excludes_file?(filename) }
|
20
20
|
linters_for_file.map do |linter|
|
21
21
|
{
|
22
22
|
linter_name: linter.class.simple_name,
|
@@ -24,31 +24,5 @@ module ERBLint
|
|
24
24
|
}
|
25
25
|
end
|
26
26
|
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def linter_enabled?(linter_class)
|
31
|
-
linter_config = @config.dig('linters', linter_class.simple_name)
|
32
|
-
return false if linter_config.nil?
|
33
|
-
linter_config['enabled'] || false
|
34
|
-
end
|
35
|
-
|
36
|
-
def linter_excludes_file?(linter, filename)
|
37
|
-
excluded_filepaths = @config.dig('linters', linter.class.simple_name, 'exclude') || []
|
38
|
-
excluded_filepaths.each do |path|
|
39
|
-
return true if File.fnmatch?(path, filename)
|
40
|
-
end
|
41
|
-
false
|
42
|
-
end
|
43
|
-
|
44
|
-
def default_config
|
45
|
-
{
|
46
|
-
'linters' => {
|
47
|
-
'FinalNewline' => {
|
48
|
-
'enabled' => true
|
49
|
-
}
|
50
|
-
}
|
51
|
-
}
|
52
|
-
end
|
53
27
|
end
|
54
28
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ERBLint
|
4
|
+
class RunnerConfig
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
def initialize(config = nil)
|
8
|
+
@config = (config || {}).dup.deep_stringify_keys
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_hash
|
12
|
+
@config.dup
|
13
|
+
end
|
14
|
+
|
15
|
+
def for_linter(klass)
|
16
|
+
klass_name = if klass.is_a?(String)
|
17
|
+
klass.to_s
|
18
|
+
elsif klass.is_a?(Class) && klass <= ERBLint::Linter
|
19
|
+
klass.simple_name
|
20
|
+
else
|
21
|
+
raise ArgumentError, 'expected String or linter class'
|
22
|
+
end
|
23
|
+
linter_klass = LinterRegistry.find_by_name(klass_name)
|
24
|
+
raise Error, "#{klass_name}: linter not found (is it loaded?)" unless linter_klass
|
25
|
+
linter_klass.config_schema.new(linters_config[klass_name] || {})
|
26
|
+
end
|
27
|
+
|
28
|
+
def merge(other_config)
|
29
|
+
self.class.new(@config.deep_merge(other_config.to_hash))
|
30
|
+
end
|
31
|
+
|
32
|
+
def merge!(other_config)
|
33
|
+
@config.deep_merge!(other_config.to_hash)
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
class << self
|
38
|
+
def default
|
39
|
+
new(
|
40
|
+
linters: {
|
41
|
+
FinalNewline: {
|
42
|
+
enabled: true,
|
43
|
+
},
|
44
|
+
},
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def linters_config
|
52
|
+
@config['linters'] || {}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
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.0.
|
4
|
+
version: 0.0.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Chan
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: better_html
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.0
|
19
|
+
version: 1.0.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.0
|
26
|
+
version: 1.0.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: html_tokenizer
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -40,6 +40,48 @@ dependencies:
|
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rubocop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.51'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.51'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activesupport
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: smart_properties
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: colorize
|
43
85
|
requirement: !ruby/object:Gem::Requirement
|
44
86
|
requirements:
|
45
87
|
- - ">="
|
@@ -83,19 +125,25 @@ dependencies:
|
|
83
125
|
description: ERB Linter tool.
|
84
126
|
email:
|
85
127
|
- justin.the.c@gmail.com
|
86
|
-
executables:
|
128
|
+
executables:
|
129
|
+
- erblint
|
87
130
|
extensions: []
|
88
131
|
extra_rdoc_files: []
|
89
132
|
files:
|
133
|
+
- exe/erblint
|
90
134
|
- lib/erb_lint.rb
|
135
|
+
- lib/erb_lint/cli.rb
|
91
136
|
- lib/erb_lint/file_loader.rb
|
92
137
|
- lib/erb_lint/linter.rb
|
138
|
+
- lib/erb_lint/linter_config.rb
|
93
139
|
- lib/erb_lint/linter_registry.rb
|
94
140
|
- lib/erb_lint/linters/deprecated_classes.rb
|
95
141
|
- lib/erb_lint/linters/erb_safety.rb
|
96
142
|
- lib/erb_lint/linters/final_newline.rb
|
97
143
|
- lib/erb_lint/linters/rubocop.rb
|
98
144
|
- lib/erb_lint/runner.rb
|
145
|
+
- lib/erb_lint/runner_config.rb
|
146
|
+
- lib/erb_lint/version.rb
|
99
147
|
homepage: https://github.com/justinthec/erb-lint
|
100
148
|
licenses:
|
101
149
|
- MIT
|