erb_lint 0.3.1 → 0.5.0

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