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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7ee1658d922ffcd67d6fac26d449d14af651c4d4
4
- data.tar.gz: 23d7dabea75b0b9cfdd788ce547e33b2584ac240
3
+ metadata.gz: eb68fe352cf7b67dba7ec568ad6d40bc87e740f5
4
+ data.tar.gz: 6cafdb08d656bcfe14666a7e00b9f144e741b62a
5
5
  SHA512:
6
- metadata.gz: 00322ff00a84d68edf04bbaf8041647b5291f1ad33543970309d3546e34018d11e3fc506be00f3ace186c473e88d7ea24a32f11af4fff0d1d124a87d9507083c
7
- data.tar.gz: 532d556c69502798da046667b90c8fdfa8ce5782a8a0a6e4c88808ac145794a95a9ffbbf4cf4097da224076738035bed14ab6e2ea9b2fadd2ea91c221522e767
6
+ metadata.gz: 1fb5769c9cd386762eae39367c60ce941719017bf20eb911d478ca17e5d8dd5f923226408369a6b870707ee15eb5f3f5c8bc5b0133535346501190e998ca75da
7
+ data.tar.gz: fcd8c9009447122ded1b08a0855bd5a60a755a71fc341a03b84995d7cb7945c1a28338e5bb64f5368d021c2ee055ded8e2a1b906dd974ec11d6cc1f159f352ca
data/exe/erblint ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift("#{__dir__}/../lib")
5
+
6
+ require 'erb_lint/cli'
7
+
8
+ cli = ERBLint::CLI.new
9
+ exit cli.run
data/lib/erb_lint.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'erb_lint/version'
4
+ require 'erb_lint/linter_config'
3
5
  require 'erb_lint/linter_registry'
4
6
  require 'erb_lint/linter'
7
+ require 'erb_lint/runner_config'
5
8
  require 'erb_lint/runner'
6
9
  require 'erb_lint/file_loader'
7
10
 
@@ -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
@@ -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
- name_parts = linter.name.split('::')
17
- name = name_parts.length < 3 ? '' : name_parts[2..-1].join('::')
18
- linter.simple_name = name
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, _config)
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
- def initialize(file_loader, config)
12
- super
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
- @deprecated_ruleset = []
15
- config.fetch('rule_set', []).each do |rule|
16
- suggestion = rule.fetch('suggestion', '')
17
- rule.fetch('deprecated', []).each do |class_expr|
18
- @deprecated_ruleset.push(
19
- class_expr: class_expr,
20
- suggestion: suggestion
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
- @addendum = config.fetch('addendum', '')
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
- iterator = build_iterator(file_content)
32
- each_class_name_with_line(iterator) do |class_name, line|
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
- each_texthtml_script(iterator) do |text_node|
36
- errors.push(*lint_file(text_node.content))
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 build_iterator(file_content)
44
- BetterHtml::NodeIterator.new(file_content, template_language: :html)
45
+ def build_parser(file_content)
46
+ BetterHtml::Parser.new(file_content, template_language: :html)
45
47
  end
46
48
 
47
- def each_class_name_with_line(iterator)
48
- each_element_with_index(iterator) do |element, _index|
49
- klass = element.find_attr('class')
50
- next unless klass
51
- klass.value_without_quotes.split(' ').each do |class_name|
52
- yield class_name, klass.name_parts.first.location.line
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 each_element_with_index(iterator)
58
- iterator.nodes.each_with_index do |node, index|
59
- yield node, index if node.element?
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 each_texthtml_script(iterator)
64
- each_element_with_index(iterator) do |element, index|
65
- type = element.find_attr('type')
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
- yield next_node if next_node&.text?
71
- end
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
- @deprecated_ruleset.select do |deprecated_rule|
87
- /\A#{deprecated_rule[:class_expr]}\z/.match(class_name)
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['better-html-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['present'].nil? ? true : config['present']
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, config_hash)
23
+ def initialize(file_loader, config)
17
24
  super
18
- @enabled_cops = config_hash.delete('only')
19
- tempfile_from('.erblint-rubocop', config_hash.except('enabled').to_yaml) do |tempfile|
20
- custom_config = RuboCop::ConfigLoader.load_file(tempfile.path)
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
- erb = BetterHtml::NodeIterator::HtmlErb.new(file_content)
28
- erb.tokens.each do |token|
29
- next unless [:stmt, :expr_literal, :expr_escaped].include?(token.type)
30
- ruby_code = token.code.sub(BLOCK_EXPR, '')
31
- offenses = inspect_content(ruby_code)
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(token, offense)
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
- @config.target_ruby_version,
67
+ @rubocop_config.target_ruby_version,
61
68
  '(erb)'
62
69
  )
63
70
  end
64
71
 
65
72
  def team
66
- selected_cops = RuboCop::Cop::Cop.all.select { |cop| cop.match?(@enabled_cops) }
67
- cop_classes = RuboCop::Cop::Registry.new(selected_cops)
68
- RuboCop::Cop::Team.new(cop_classes, @config, extra_details: true, display_cop_names: true)
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(token, offense)
85
+ def format_error(code_node, offense)
72
86
  {
73
- line: token.location.line + offense.line - 1,
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
@@ -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 = default_config.merge(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
- @linters = LinterRegistry.linters.select { |linter_class| linter_enabled?(linter_class) }
12
- @linters.map! do |linter_class|
13
- linter_config = @config.dig('linters', linter_class.simple_name)
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| linter_excludes_file?(linter, filename) }
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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ERBLint
4
+ VERSION = '0.0.11'
5
+ 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.9
4
+ version: 0.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Chan
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2017-11-14 00:00:00.000000000 Z
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.10
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.10
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