erb_lint 0.3.1 → 0.5.0

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
  SHA256:
3
- metadata.gz: 1bd25c794b1028b09ecec8ce3b5e77600d36364d59b85f2e4b24a18d6bb08853
4
- data.tar.gz: e41f353e930401a024d880fc6973beb1e462f2f5372edb094523eacde0030d72
3
+ metadata.gz: 98ba7a87f348e584502a9e4810f9311329c04aeddf34af0b05a37702648b495e
4
+ data.tar.gz: 8728892a4c09fcbdbddb60316b911bf73ce099cc04e9ac10534b76f8aa6708a5
5
5
  SHA512:
6
- metadata.gz: 94ed5b3479803ff2b51af6390c0e39c2fb37ef98e23fc2523ac204b638d8ff043665926081794ff27d77a60faae8ee7747cb4ef3d04125b7b3f7c2ae55ea268f
7
- data.tar.gz: 9b423110f202bb9728ec57dbd60f04aa86e2901ecfa915d9ddc49fd58fac27b3daddea78780612c3f474ece4c41c01977bea72ad8353a9a113d5cee8c4a51914
6
+ metadata.gz: 8d01f3c07ddebb9482ee744c69df6e799b4c0cb06bd24ffd7939f3d86e612a4f1bb2d5b4040c8505d7c8f8e9cceaecaabcca7dc03cab6ea5f23e73f10e098345
7
+ data.tar.gz: '080398f6522352b669bf21ea1fbdd8850b7b6b8dcef60493d8349fbfeb98eceeefc77c15442cad339612e83c5056d5498a286046c3a3749a73428661c962b695'
data/lib/erb_lint/cli.rb CHANGED
@@ -69,6 +69,7 @@ module ERBLint
69
69
 
70
70
  @options[:format] ||= :multiline
71
71
  @options[:fail_level] ||= severity_level_for_name(:refactor)
72
+ @options[:disable_inline_configs] ||= false
72
73
  @stats.files = lint_files.size
73
74
  @stats.linters = enabled_linter_classes.size
74
75
  @stats.autocorrectable_linters = enabled_linter_classes.count(&:support_autocorrect?)
@@ -76,7 +77,7 @@ module ERBLint
76
77
  reporter = Reporter.create_reporter(@options[:format], @stats, autocorrect?)
77
78
  reporter.preview
78
79
 
79
- runner = ERBLint::Runner.new(file_loader, @config)
80
+ runner = ERBLint::Runner.new(file_loader, @config, @options[:disable_inline_configs])
80
81
  file_content = nil
81
82
 
82
83
  lint_files.each do |filename|
@@ -221,7 +222,7 @@ module ERBLint
221
222
  rescue Psych::SyntaxError => e
222
223
  failure!("error parsing config: #{e.message}")
223
224
  ensure
224
- @config.merge!(runner_config_override)
225
+ @config&.merge!(runner_config_override)
225
226
  end
226
227
 
227
228
  def file_loader
@@ -374,6 +375,10 @@ module ERBLint
374
375
  @options[:allow_no_files] = config
375
376
  end
376
377
 
378
+ opts.on("--disable-inline-configs", "Report all offenses while ignoring inline disable comments") do
379
+ @options[:disable_inline_configs] = true
380
+ end
381
+
377
382
  opts.on(
378
383
  "-sFILE",
379
384
  "--stdin FILE",
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "erb_lint/utils/inline_configs"
4
+
3
5
  module ERBLint
4
6
  # Defines common functionality available to all linters.
5
7
  class Linter
@@ -53,6 +55,13 @@ module ERBLint
53
55
  raise NotImplementedError, "must implement ##{__method__}"
54
56
  end
55
57
 
58
+ def run_and_update_offense_status(processed_source, enable_inline_configs = true)
59
+ run(processed_source)
60
+ if @offenses.any? && enable_inline_configs
61
+ update_offense_status(processed_source)
62
+ end
63
+ end
64
+
56
65
  def add_offense(source_range, message, context = nil, severity = nil)
57
66
  @offenses << Offense.new(self, source_range, message, context, severity)
58
67
  end
@@ -60,5 +69,22 @@ module ERBLint
60
69
  def clear_offenses
61
70
  @offenses = []
62
71
  end
72
+
73
+ private
74
+
75
+ def update_offense_status(processed_source)
76
+ @offenses.each do |offense|
77
+ offense_line_range = offense.source_range.line_range
78
+ offense_lines = source_for_line_range(processed_source, offense_line_range)
79
+
80
+ if Utils::InlineConfigs.rule_disable_comment_for_lines?(self.class.simple_name, offense_lines)
81
+ offense.disabled = true
82
+ end
83
+ end
84
+ end
85
+
86
+ def source_for_line_range(processed_source, line_range)
87
+ processed_source.source_buffer.source_lines[line_range.first - 1..line_range.last - 1].join
88
+ end
63
89
  end
64
90
  end
@@ -17,7 +17,7 @@ module ERBLint
17
17
  ALLOWED_CORRECTORS = ["I18nCorrector", "RuboCop::Corrector::I18n::HardCodedString"]
18
18
 
19
19
  NON_TEXT_TAGS = Set.new(["script", "style", "xmp", "iframe", "noembed", "noframes", "listing"])
20
- TEXT_NOT_ALLOWED = Set.new([
20
+ NO_TRANSLATION_NEEDED = Set.new([
21
21
  "&nbsp;",
22
22
  "&amp;",
23
23
  "&lt;",
@@ -40,6 +40,7 @@ module ERBLint
40
40
  "&ensp;",
41
41
  "&emsp;",
42
42
  "&thinsp;",
43
+ "&times;",
43
44
  ])
44
45
 
45
46
  class ConfigSchema < LinterConfig
@@ -96,7 +97,7 @@ module ERBLint
96
97
 
97
98
  def check_string?(str)
98
99
  string = str.gsub(/\s*/, "")
99
- string.length > 1 && !TEXT_NOT_ALLOWED.include?(string)
100
+ string.length > 1 && !NO_TRANSLATION_NEEDED.include?(string)
100
101
  end
101
102
 
102
103
  def load_corrector
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb_lint/utils/inline_configs"
4
+
5
+ module ERBLint
6
+ module Linters
7
+ # Checks for unused disable comments.
8
+ class NoUnusedDisable < Linter
9
+ include LinterRegistry
10
+
11
+ def run(processed_source, offenses)
12
+ disabled_rules_and_line_number = {}
13
+
14
+ processed_source.source_buffer.source_lines.each_with_index do |line, index|
15
+ rule_disables = Utils::InlineConfigs.disabled_rules(line)
16
+ next unless rule_disables
17
+
18
+ rule_disables.split(",").each do |rule|
19
+ disabled_rules_and_line_number[rule.strip] =
20
+ (disabled_rules_and_line_number[rule.strip] ||= []).push(index + 1)
21
+ end
22
+ end
23
+
24
+ offenses.each do |offense|
25
+ rule_name = offense.linter.class.simple_name
26
+ line_numbers = disabled_rules_and_line_number[rule_name]
27
+ next unless line_numbers
28
+
29
+ line_numbers.reject do |line_number|
30
+ if (offense.source_range.line_span.first..offense.source_range.line_span.last).include?(line_number)
31
+ disabled_rules_and_line_number[rule_name].delete(line_number)
32
+ end
33
+ end
34
+ end
35
+
36
+ disabled_rules_and_line_number.each do |rule, line_numbers|
37
+ line_numbers.each do |line_number|
38
+ add_offense(processed_source.source_buffer.line_range(line_number),
39
+ "Unused erblint:disable comment for #{rule}")
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -15,6 +15,7 @@ module ERBLint
15
15
  @message = message
16
16
  @context = context
17
17
  @severity = severity
18
+ @disabled = false
18
19
  end
19
20
 
20
21
  def to_cached_offense_hash
@@ -44,6 +45,12 @@ module ERBLint
44
45
  line_range.begin
45
46
  end
46
47
 
48
+ attr_writer :disabled
49
+
50
+ def disabled?
51
+ @disabled
52
+ end
53
+
47
54
  def column
48
55
  source_range.column
49
56
  end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rexml/document"
4
+ require "rexml/formatters/pretty"
5
+
6
+ module ERBLint
7
+ module Reporters
8
+ class JunitReporter < Reporter
9
+ def preview; end
10
+
11
+ def show
12
+ xml = create_junit_xml
13
+ formatted_xml_string = StringIO.new
14
+ REXML::Formatters::Pretty.new.write(xml, formatted_xml_string)
15
+ puts formatted_xml_string.string
16
+ end
17
+
18
+ private
19
+
20
+ CONTEXT = {
21
+ prologue_quote: :quote,
22
+ attribute_quote: :quote,
23
+ }
24
+
25
+ def create_junit_xml
26
+ # create prologue
27
+ xml = REXML::Document.new(nil, CONTEXT)
28
+ xml << REXML::XMLDecl.new("1.0", "UTF-8")
29
+
30
+ xml.add_element(create_testsuite_element)
31
+
32
+ xml
33
+ end
34
+
35
+ def create_testsuite_element
36
+ tests = stats.processed_files.size
37
+ failures = stats.found
38
+ testsuite_element = REXML::Element.new("testsuite", nil, CONTEXT)
39
+ testsuite_element.add_attribute("name", "erblint")
40
+ testsuite_element.add_attribute("tests", tests.to_s)
41
+ testsuite_element.add_attribute("failures", failures.to_s)
42
+
43
+ testsuite_element.add_element(create_properties)
44
+
45
+ processed_files.each do |filename, offenses|
46
+ if offenses.empty?
47
+ testcase_element = REXML::Element.new("testcase", nil, CONTEXT)
48
+ testcase_element.add_attribute("name", filename.to_s)
49
+ testcase_element.add_attribute("file", filename.to_s)
50
+
51
+ testsuite_element.add_element(testcase_element)
52
+ end
53
+
54
+ offenses.each do |offense|
55
+ testsuite_element.add_element(create_testcase(filename, offense))
56
+ end
57
+ end
58
+
59
+ testsuite_element
60
+ end
61
+
62
+ def create_properties
63
+ properties_element = REXML::Element.new("properties", nil, CONTEXT)
64
+
65
+ [
66
+ ["erb_lint_version", ERBLint::VERSION],
67
+ ["ruby_engine", RUBY_ENGINE],
68
+ ["ruby_version", RUBY_VERSION],
69
+ ["ruby_patchlevel", RUBY_PATCHLEVEL.to_s],
70
+ ["ruby_platform", RUBY_PLATFORM],
71
+ ].each do |property_attribute|
72
+ properties_element.add_element(create_property(*property_attribute))
73
+ end
74
+
75
+ properties_element
76
+ end
77
+
78
+ def create_property(name, value)
79
+ property_element = REXML::Element.new("property")
80
+ property_element.add_attribute("name", name)
81
+ property_element.add_attribute("value", value)
82
+
83
+ property_element
84
+ end
85
+
86
+ def create_testcase(filename, offense)
87
+ testcase_element = REXML::Element.new("testcase", nil, CONTEXT)
88
+ testcase_element.add_attribute("name", filename.to_s)
89
+ testcase_element.add_attribute("file", filename.to_s)
90
+ testcase_element.add_attribute("lineno", offense.line_number.to_s)
91
+
92
+ testcase_element.add_element(create_failure(filename, offense))
93
+
94
+ testcase_element
95
+ end
96
+
97
+ def create_failure(filename, offense)
98
+ message = offense.message
99
+ type = offense.simple_name
100
+
101
+ failure_element = REXML::Element.new("failure", nil, CONTEXT)
102
+ failure_element.add_attribute("message", "#{type}: #{message}")
103
+ failure_element.add_attribute("type", type.to_s)
104
+
105
+ cdata_element = REXML::CData.new("#{type}: #{message} at #{filename}:#{offense.line_number}:#{offense.column}")
106
+ failure_element.add_text(cdata_element)
107
+
108
+ failure_element
109
+ end
110
+ end
111
+ end
112
+ end
@@ -5,15 +5,19 @@ module ERBLint
5
5
  class Runner
6
6
  attr_reader :offenses
7
7
 
8
- def initialize(file_loader, config)
8
+ def initialize(file_loader, config, disable_inline_configs = false)
9
9
  @file_loader = file_loader
10
10
  @config = config || RunnerConfig.default
11
11
  raise ArgumentError, "expect `config` to be a RunnerConfig instance" unless @config.is_a?(RunnerConfig)
12
12
 
13
- linter_classes = LinterRegistry.linters.select { |klass| @config.for_linter(klass).enabled? }
13
+ linter_classes = LinterRegistry.linters.select do |klass|
14
+ @config.for_linter(klass).enabled? && klass != ERBLint::Linters::NoUnusedDisable
15
+ end
14
16
  @linters = linter_classes.map do |linter_class|
15
17
  linter_class.new(@file_loader, @config.for_linter(linter_class))
16
18
  end
19
+ @no_unused_disable = nil
20
+ @disable_inline_configs = disable_inline_configs
17
21
  @offenses = []
18
22
  end
19
23
 
@@ -21,18 +25,43 @@ module ERBLint
21
25
  @linters
22
26
  .reject { |linter| linter.excludes_file?(processed_source.filename) }
23
27
  .each do |linter|
24
- linter.run(processed_source)
28
+ linter.run_and_update_offense_status(processed_source, enable_inline_configs?)
25
29
  @offenses.concat(linter.offenses)
26
30
  end
31
+ report_unused_disable(processed_source)
32
+ @offenses = @offenses.reject(&:disabled?)
27
33
  end
28
34
 
29
35
  def clear_offenses
30
36
  @offenses = []
31
37
  @linters.each(&:clear_offenses)
38
+ @no_unused_disable&.clear_offenses
32
39
  end
33
40
 
34
41
  def restore_offenses(offenses)
35
42
  @offenses.concat(offenses)
36
43
  end
44
+
45
+ private
46
+
47
+ def enable_inline_configs?
48
+ !@disable_inline_configs
49
+ end
50
+
51
+ def no_unused_disable_enabled?
52
+ LinterRegistry.linters.include?(ERBLint::Linters::NoUnusedDisable) &&
53
+ @config.for_linter(ERBLint::Linters::NoUnusedDisable).enabled?
54
+ end
55
+
56
+ def report_unused_disable(processed_source)
57
+ if no_unused_disable_enabled? && enable_inline_configs?
58
+ @no_unused_disable = ERBLint::Linters::NoUnusedDisable.new(
59
+ @file_loader,
60
+ @config.for_linter(ERBLint::Linters::NoUnusedDisable)
61
+ )
62
+ @no_unused_disable.run(processed_source, @offenses)
63
+ @offenses.concat(@no_unused_disable.offenses)
64
+ end
65
+ end
37
66
  end
38
67
  end
@@ -83,7 +83,7 @@ module ERBLint
83
83
  def config_hash_for_linter(klass_name)
84
84
  config_hash = linters_config[klass_name] || {}
85
85
  config_hash["exclude"] ||= []
86
- config_hash["exclude"].concat(global_exclude) if config_hash["exclude"].is_a?(Array)
86
+ config_hash["exclude"].concat(global_exclude).uniq! if config_hash["exclude"].is_a?(Array)
87
87
  config_hash
88
88
  end
89
89
 
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ERBLint
4
+ module Utils
5
+ class InlineConfigs
6
+ def self.rule_disable_comment_for_lines?(rule, lines)
7
+ lines.match?(/# erblint:disable (?<rules>.*#{rule}).*/)
8
+ end
9
+
10
+ def self.disabled_rules(line)
11
+ line.match(/# erblint:disable (?<rules>.*) %>/)&.named_captures&.fetch("rules")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ERBLint
4
- VERSION = "0.3.1"
4
+ VERSION = "0.5.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: erb_lint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Chan
8
+ - Shopify Developers
8
9
  autorequire:
9
10
  bindir: exe
10
11
  cert_chain: []
11
- date: 2022-11-10 00:00:00.000000000 Z
12
+ date: 2023-08-25 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: activesupport
@@ -138,7 +139,7 @@ dependencies:
138
139
  version: '0'
139
140
  description: ERB Linter tool.
140
141
  email:
141
- - justin.the.c@gmail.com
142
+ - ruby@shopify.com
142
143
  executables:
143
144
  - erblint
144
145
  extensions: []
@@ -164,6 +165,7 @@ files:
164
165
  - lib/erb_lint/linters/final_newline.rb
165
166
  - lib/erb_lint/linters/hard_coded_string.rb
166
167
  - lib/erb_lint/linters/no_javascript_tag_helper.rb
168
+ - lib/erb_lint/linters/no_unused_disable.rb
167
169
  - lib/erb_lint/linters/parser_errors.rb
168
170
  - lib/erb_lint/linters/partial_instance_variable.rb
169
171
  - lib/erb_lint/linters/require_input_autocomplete.rb
@@ -181,12 +183,14 @@ files:
181
183
  - lib/erb_lint/reporter.rb
182
184
  - lib/erb_lint/reporters/compact_reporter.rb
183
185
  - lib/erb_lint/reporters/json_reporter.rb
186
+ - lib/erb_lint/reporters/junit_reporter.rb
184
187
  - lib/erb_lint/reporters/multiline_reporter.rb
185
188
  - lib/erb_lint/runner.rb
186
189
  - lib/erb_lint/runner_config.rb
187
190
  - lib/erb_lint/runner_config_resolver.rb
188
191
  - lib/erb_lint/stats.rb
189
192
  - lib/erb_lint/utils/block_map.rb
193
+ - lib/erb_lint/utils/inline_configs.rb
190
194
  - lib/erb_lint/utils/offset_corrector.rb
191
195
  - lib/erb_lint/utils/ruby_to_erb.rb
192
196
  - lib/erb_lint/utils/severity_levels.rb
@@ -211,7 +215,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
211
215
  - !ruby/object:Gem::Version
212
216
  version: '0'
213
217
  requirements: []
214
- rubygems_version: 3.3.3
218
+ rubygems_version: 3.4.18
215
219
  signing_key:
216
220
  specification_version: 4
217
221
  summary: ERB lint tool