slim_lint_standard 0.0.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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +21 -0
  3. data/bin/slim-lint-standard +7 -0
  4. data/config/default.yml +109 -0
  5. data/lib/slim_lint/atom.rb +129 -0
  6. data/lib/slim_lint/capture_map.rb +19 -0
  7. data/lib/slim_lint/cli.rb +167 -0
  8. data/lib/slim_lint/configuration.rb +111 -0
  9. data/lib/slim_lint/configuration_loader.rb +86 -0
  10. data/lib/slim_lint/constants.rb +10 -0
  11. data/lib/slim_lint/document.rb +78 -0
  12. data/lib/slim_lint/engine.rb +41 -0
  13. data/lib/slim_lint/exceptions.rb +20 -0
  14. data/lib/slim_lint/file_finder.rb +88 -0
  15. data/lib/slim_lint/filter.rb +126 -0
  16. data/lib/slim_lint/filters/attribute_processor.rb +46 -0
  17. data/lib/slim_lint/filters/auto_indenter.rb +39 -0
  18. data/lib/slim_lint/filters/control_processor.rb +46 -0
  19. data/lib/slim_lint/filters/do_inserter.rb +39 -0
  20. data/lib/slim_lint/filters/end_inserter.rb +74 -0
  21. data/lib/slim_lint/filters/interpolation.rb +73 -0
  22. data/lib/slim_lint/filters/multi_flattener.rb +32 -0
  23. data/lib/slim_lint/filters/splat_processor.rb +20 -0
  24. data/lib/slim_lint/filters/static_merger.rb +47 -0
  25. data/lib/slim_lint/lint.rb +70 -0
  26. data/lib/slim_lint/linter/avoid_multiline_expressions.rb +41 -0
  27. data/lib/slim_lint/linter/comment_control_statement.rb +26 -0
  28. data/lib/slim_lint/linter/consecutive_control_statements.rb +26 -0
  29. data/lib/slim_lint/linter/control_statement_spacing.rb +32 -0
  30. data/lib/slim_lint/linter/dynamic_output_spacing.rb +77 -0
  31. data/lib/slim_lint/linter/embedded_engines.rb +18 -0
  32. data/lib/slim_lint/linter/empty_control_statement.rb +15 -0
  33. data/lib/slim_lint/linter/empty_lines.rb +24 -0
  34. data/lib/slim_lint/linter/file_length.rb +18 -0
  35. data/lib/slim_lint/linter/line_length.rb +18 -0
  36. data/lib/slim_lint/linter/redundant_div.rb +21 -0
  37. data/lib/slim_lint/linter/rubocop.rb +131 -0
  38. data/lib/slim_lint/linter/standard.rb +69 -0
  39. data/lib/slim_lint/linter/tab.rb +20 -0
  40. data/lib/slim_lint/linter/tag_case.rb +15 -0
  41. data/lib/slim_lint/linter/trailing_blank_lines.rb +19 -0
  42. data/lib/slim_lint/linter/trailing_whitespace.rb +17 -0
  43. data/lib/slim_lint/linter.rb +93 -0
  44. data/lib/slim_lint/linter_registry.rb +37 -0
  45. data/lib/slim_lint/linter_selector.rb +87 -0
  46. data/lib/slim_lint/logger.rb +103 -0
  47. data/lib/slim_lint/matcher/anything.rb +11 -0
  48. data/lib/slim_lint/matcher/base.rb +21 -0
  49. data/lib/slim_lint/matcher/capture.rb +32 -0
  50. data/lib/slim_lint/matcher/nothing.rb +13 -0
  51. data/lib/slim_lint/options.rb +110 -0
  52. data/lib/slim_lint/parser.rb +584 -0
  53. data/lib/slim_lint/rake_task.rb +125 -0
  54. data/lib/slim_lint/report.rb +25 -0
  55. data/lib/slim_lint/reporter/checkstyle_reporter.rb +42 -0
  56. data/lib/slim_lint/reporter/default_reporter.rb +40 -0
  57. data/lib/slim_lint/reporter/emacs_reporter.rb +40 -0
  58. data/lib/slim_lint/reporter/json_reporter.rb +50 -0
  59. data/lib/slim_lint/reporter.rb +44 -0
  60. data/lib/slim_lint/ruby_extract_engine.rb +30 -0
  61. data/lib/slim_lint/ruby_extractor.rb +175 -0
  62. data/lib/slim_lint/ruby_parser.rb +32 -0
  63. data/lib/slim_lint/runner.rb +82 -0
  64. data/lib/slim_lint/sexp.rb +134 -0
  65. data/lib/slim_lint/sexp_visitor.rb +150 -0
  66. data/lib/slim_lint/source_location.rb +45 -0
  67. data/lib/slim_lint/utils.rb +84 -0
  68. data/lib/slim_lint/version.rb +6 -0
  69. data/lib/slim_lint.rb +55 -0
  70. metadata +218 -0
@@ -0,0 +1,32 @@
1
+ module SlimLint
2
+ module Filters
3
+ # Flattens nested multi expressions while respecting source locatoins.
4
+ #
5
+ # @api public
6
+ class MultiFlattener < Filter
7
+ def on_slim_embedded(*args)
8
+ @self
9
+ end
10
+
11
+ def on_multi(*exps)
12
+ # If the multi contains a single element, just return the element
13
+ return compile(exps.first) if exps.size == 1
14
+
15
+ result = @self
16
+ result.clear
17
+ result.concat(@key)
18
+
19
+ exps.each do |exp|
20
+ exp = compile(exp)
21
+ if exp.first == :multi
22
+ result.concat(exp[1..])
23
+ else
24
+ result << exp
25
+ end
26
+ end
27
+
28
+ result
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlimLint
4
+ module Filters
5
+ # A dumbed-down version of {Slim::Splat::Filter} which doesn't introduced
6
+ # temporary variables or other cruft.
7
+ class SplatProcessor < Filter
8
+ # Handle slim splat expressions `[:slim, :splat, code]`
9
+ #
10
+ # @param code [String]
11
+ # @return [Array]
12
+ def on_slim_splat(code)
13
+ return code if code[0] == :multi
14
+ @self.delete_at(1)
15
+ @self.first.value = :code
16
+ @self
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,47 @@
1
+ module SlimLint
2
+ module Filters
3
+ # Merges several statics into a single static while respecting source
4
+ # location data. Example:
5
+ #
6
+ # [:multi,
7
+ # [:static, "Hello "],
8
+ # [:static, "World!"]]
9
+ #
10
+ # Compiles to:
11
+ #
12
+ # [:static, "Hello World!"]
13
+ #
14
+ # @api public
15
+ class StaticMerger < Filter
16
+ def on_slim_embedded(*exps)
17
+ @self
18
+ end
19
+
20
+ def on_multi(*exps)
21
+ result = @self
22
+ result.clear
23
+ result.concat(@key)
24
+
25
+ static = nil
26
+ exps.each do |exp|
27
+ if exp.first == :static
28
+ if static
29
+ static.finish = exp.finish if later_pos?(static.finish, exp.finish)
30
+ static.last.finish = exp.finish if later_pos?(static.last.finish, exp.finish)
31
+ static.last.value << exp.last.value
32
+ else
33
+ static = exp
34
+ static[1] = exp.last.dup
35
+ result << static
36
+ end
37
+ else
38
+ result << compile(exp)
39
+ static = nil unless exp.first == :newline
40
+ end
41
+ end
42
+
43
+ result.size == 2 ? result[1] : result
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlimLint
4
+ # Contains information about a problem or issue with a Slim document.
5
+ class Lint
6
+ # @return [String] file path to which the lint applies
7
+ attr_reader :filename
8
+
9
+ # @return [SourceLocation] location in the file the lint corresponds to
10
+ attr_reader :location
11
+
12
+ # @return [SlimLint::Linter] linter that reported the lint
13
+ attr_reader :linter
14
+
15
+ # @return [String] sublinter that reported the lint
16
+ attr_reader :sublinter
17
+
18
+ # @return [String] error/warning message to display to user
19
+ attr_reader :message
20
+
21
+ # @return [Symbol] whether this lint is a warning or an error
22
+ attr_reader :severity
23
+
24
+ # Creates a new lint.
25
+ #
26
+ # @param linter [SlimLint::Linter]
27
+ # @param filename [String]
28
+ # @param location [SourceLocation]
29
+ # @param message [String]
30
+ # @param severity [Symbol]
31
+ def initialize(linter, filename, location, message, severity = :warning)
32
+ @linter, @sublinter = Array(linter)
33
+ @filename = filename
34
+ @location = location
35
+ @message = message
36
+ @severity = severity
37
+ end
38
+
39
+ def line
40
+ location.line
41
+ end
42
+
43
+ def column
44
+ location.column
45
+ end
46
+
47
+ def last_line
48
+ location.last_line
49
+ end
50
+
51
+ def last_column
52
+ location.last_column
53
+ end
54
+
55
+ def cop
56
+ @sublinter || @linter.name if @linter
57
+ end
58
+
59
+ def name
60
+ [@linter.name, @sublinter].compact.join("/") if @linter
61
+ end
62
+
63
+ # Return whether this lint has a severity of error.
64
+ #
65
+ # @return [Boolean]
66
+ def error?
67
+ @severity == :error
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlimLint
4
+ # Searches for multi-line control statements, dynamic output statements,
5
+ # attribute values, and splats.
6
+ class Linter::AvoidMultilineExpressions < Linter
7
+ include LinterRegistry
8
+
9
+ on [:slim, :control] do |sexp|
10
+ _, _, code = sexp
11
+ next unless code.size > 2
12
+
13
+ msg = "Avoid control statements that span multiple lines."
14
+ report_lint(sexp, msg)
15
+ end
16
+
17
+ on [:slim, :output] do |sexp|
18
+ _, _, _, code = sexp
19
+ next unless code.size > 2
20
+
21
+ msg = "Avoid dynamic output statements that span multiple lines."
22
+ report_lint(sexp, msg)
23
+ end
24
+
25
+ on [:slim, :attrvalue] do |sexp|
26
+ _, _, _, code = sexp
27
+ next unless code.size > 2
28
+
29
+ msg = "Avoid attribute values that span multiple lines."
30
+ report_lint(sexp, msg)
31
+ end
32
+
33
+ on [:slim, :splat] do |sexp|
34
+ _, _, code = sexp
35
+ next unless code.size > 2
36
+
37
+ msg = "Avoid attribute values that span multiple lines."
38
+ report_lint(sexp, msg)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlimLint
4
+ # Searches for control statements with only comments.
5
+ class Linter::CommentControlStatement < Linter
6
+ include LinterRegistry
7
+
8
+ RUBOCOP_CONTROL_COMMENT_RE = /^\s*(rubocop|standard):\w+/
9
+ TEMPLATE_DEPENDENCY_CONTROL_COMMENT_RE = /^\s*Template Dependency:/
10
+
11
+ on [:slim, :control] do |sexp|
12
+ _, _, code = sexp
13
+ next unless code.last[1][/\A\s*#/]
14
+
15
+ comment = code.last[1][/\A\s*#(.*\z)/, 1]
16
+
17
+ next if RUBOCOP_CONTROL_COMMENT_RE.match?(comment)
18
+ next if TEMPLATE_DEPENDENCY_CONTROL_COMMENT_RE.match?(comment)
19
+
20
+ msg =
21
+ "Slim code comments (`/#{comment}`) are preferred over " \
22
+ "control statement comments (`-##{comment}`)"
23
+ report_lint(sexp, msg)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlimLint
4
+ # Searches for more than an allowed number of consecutive control code
5
+ # statements that could be condensed into a :ruby filter.
6
+ class Linter::ConsecutiveControlStatements < Linter
7
+ include LinterRegistry
8
+
9
+ on [:multi] do |sexp|
10
+ max = config["max_consecutive"] + 1
11
+ Utils.for_consecutive_items(sexp, method(:flat_control_statement?), max) do |group|
12
+ report_lint(
13
+ group.first,
14
+ "#{group.count} consecutive control statements can be " \
15
+ "merged into a single `ruby:` filter"
16
+ )
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def flat_control_statement?(sexp)
23
+ sexp.match?([:slim, :control]) && sexp[3] == [:multi]
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlimLint
4
+ # Checks for missing or superfluous spacing before and after control statements.
5
+ class Linter::ControlStatementSpacing < Linter
6
+ include LinterRegistry
7
+
8
+ on [:slim, :control] do |sexp|
9
+ expr = sexp.last[0]
10
+ expr_line, expr_col = sexp.start
11
+ line = document.source_lines[expr_line - 1][(expr_col - 1)..]
12
+ after_pattern, after_action = after_config
13
+
14
+ unless line.match?(after_pattern)
15
+ report_lint(expr, "Please #{after_action} the dash")
16
+ end
17
+ end
18
+
19
+ def after_config
20
+ @after_config ||= case config["space_after"]
21
+ when "never", false, nil
22
+ [/^ *-#?[^# ]/, "remove spaces after"]
23
+ when "always", "single", true
24
+ [/^ *-#? [^ ]/, "use one space after"]
25
+ when "ignore", "any"
26
+ [//, ""]
27
+ else
28
+ raise ArgumentError, "Unknown value for `space_after`; please use 'never' or 'always'"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlimLint
4
+ # Checks for missing or superfluous spacing before and after dynamic tag output indicators.
5
+ class Linter::DynamicOutputSpacing < Linter
6
+ include LinterRegistry
7
+
8
+ PATTERN = "==?['<>]*"
9
+
10
+ on [:html, :tag, anything, [], capture(:expr, [:slim, :output, anything, anything])] do |sexp|
11
+ # Fetch original Slim code that contains an element with a control statement.
12
+ expr_line, expr_col = captures[:expr].start
13
+ line = document.source_lines[expr_line - 1][(expr_col - 1)..]
14
+
15
+ before_pattern, _ = before_config
16
+ after_pattern, _ = after_config
17
+
18
+ report(captures[:expr], line.match?(before_pattern), line.match?(after_pattern))
19
+
20
+ # Visit any children of the HTML tag, but don't _revisit_ this Slim output.
21
+ traverse_children(captures[:expr].last)
22
+ :stop
23
+ end
24
+
25
+ on [:slim, :output] do |sexp|
26
+ expr_line, expr_col = sexp.start
27
+ line = document.source_lines[expr_line - 1][(expr_col - 1)..]
28
+ after_pattern, _ = after_config
29
+
30
+ report(sexp, true, line.match?(after_pattern))
31
+ end
32
+
33
+ def report(expr, *results)
34
+ _, before_action = before_config
35
+ _, after_action = after_config
36
+
37
+ case results
38
+ when [false, true]
39
+ report_lint(expr, "Please #{before_action} the equals sign")
40
+ when [true, false]
41
+ report_lint(expr, "Please #{after_action} the equals sign")
42
+ when [false, false]
43
+ if before_action[0] == after_action[0]
44
+ report_lint(expr, "Please #{before_action} and after the equals sign")
45
+ else
46
+ report_lint(expr, "Please #{before_action} and #{after_action} the equals sign")
47
+ end
48
+ end
49
+ end
50
+
51
+ def before_config
52
+ @before_config ||= case config["space_before"]
53
+ when "never", false, nil
54
+ [/^#{PATTERN}/, "remove spaces before"]
55
+ when "always", "single", true
56
+ [/^ #{PATTERN}/, "use one space before"]
57
+ when "ignore", "any"
58
+ [//, ""]
59
+ else
60
+ raise ArgumentError, "Unknown value for `space_before`; please use 'never', 'always', or 'ignore'"
61
+ end
62
+ end
63
+
64
+ def after_config
65
+ @after_config ||= case config["space_after"]
66
+ when "never", false, nil
67
+ [/^ *#{PATTERN}[^ ]/, "remove spaces after"]
68
+ when "always", "single", true
69
+ [/^ *#{PATTERN} [^ ]/, "use one space after"]
70
+ when "ignore", "any"
71
+ [//, ""]
72
+ else
73
+ raise ArgumentError, "Unknown value for `space_after`; please use 'never', 'always', or 'ignore'"
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlimLint
4
+ # Checks for forbidden embedded engines.
5
+ class Linter::EmbeddedEngines < Linter
6
+ include LinterRegistry
7
+
8
+ MESSAGE = "Forbidden embedded engine `%s` found"
9
+
10
+ on [:slim, :embedded] do |sexp|
11
+ _, _, engine, _ = sexp
12
+
13
+ forbidden_engines = config["forbidden_engines"]
14
+ next unless forbidden_engines.include?(engine)
15
+ report_lint(sexp, MESSAGE % engine)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlimLint
4
+ # Searches for control statements with no code.
5
+ class Linter::EmptyControlStatement < Linter
6
+ include LinterRegistry
7
+
8
+ on [:slim, :control] do |sexp|
9
+ _, _, code = sexp
10
+ next unless code.last[1][/\A\s*\Z/]
11
+
12
+ report_lint(sexp, "Empty control statement can be removed")
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlimLint
4
+ # This linter checks for two or more consecutive blank lines
5
+ # and for the first blank line in file.
6
+ class Linter::EmptyLines < Linter
7
+ include LinterRegistry
8
+
9
+ on_start do |_sexp|
10
+ was_empty = true
11
+ document.source.lines.each.with_index(1) do |line, i|
12
+ if line.blank?
13
+ if was_empty
14
+ sexp = Sexp.new(start: [i, 0], finish: [i, 0])
15
+ report_lint(sexp, "Extra empty line detected")
16
+ end
17
+ was_empty = true
18
+ else
19
+ was_empty = false
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlimLint
4
+ # Checks for file longer than a maximum number of lines.
5
+ class Linter::FileLength < Linter
6
+ include LinterRegistry
7
+
8
+ MSG = "File is too long. [%d/%d]"
9
+
10
+ on_start do |_sexp|
11
+ count = document.source_lines.size
12
+ if count > config["max"]
13
+ sexp = Sexp.new(start: [1, 0], finish: [1, 0])
14
+ report_lint(sexp, format(MSG, count, config["max"]))
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlimLint
4
+ # Checks for lines longer than a maximum number of columns.
5
+ class Linter::LineLength < Linter
6
+ include LinterRegistry
7
+
8
+ MSG = "Line is too long. [%d/%d]"
9
+
10
+ on_start do |_sexp|
11
+ document.source_lines.each.with_index(1) do |line, i|
12
+ next if line.length <= config["max"]
13
+ sexp = Sexp.new(start: [i, 0], finish: [i, 0])
14
+ report_lint(sexp, format(MSG, line.length, config["max"]))
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlimLint
4
+ # Checks for unnecessary uses of the `div` tag where a class name or ID
5
+ # already implies a div.
6
+ class Linter::RedundantDiv < Linter
7
+ include LinterRegistry
8
+
9
+ SHORTCUT_ATTRS = %w[id class]
10
+ MESSAGE = "`div` is redundant when %s attribute shortcut is present"
11
+
12
+ on [:html, :tag, "div", capture(:attrs, [:html, :attrs]), anything] do |sexp|
13
+ _, _, name, value = captures[:attrs][2]
14
+ next unless name
15
+ next unless value[0] == :static
16
+ next unless SHORTCUT_ATTRS.include?(name.value)
17
+
18
+ report_lint(sexp[2], MESSAGE % name)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "slim_lint/ruby_extractor"
4
+ require "slim_lint/ruby_extract_engine"
5
+ require "rubocop"
6
+
7
+ module SlimLint
8
+ class Linter
9
+ # Runs RuboCop on Ruby code extracted from Slim templates.
10
+ class RuboCop < Linter
11
+ include LinterRegistry
12
+
13
+ on_start do |_sexp|
14
+ processed_sexp = SlimLint::RubyExtractEngine.new.call(document.source)
15
+
16
+ extractor = SlimLint::RubyExtractor.new
17
+ extracted_source = extractor.extract(processed_sexp)
18
+
19
+ next if extracted_source.source.empty?
20
+
21
+ find_lints(extracted_source.source, extracted_source.source_map)
22
+ end
23
+
24
+ private
25
+
26
+ # Executes RuboCop against the given Ruby code and records the offenses as
27
+ # lints.
28
+ #
29
+ # @param ruby [String] Ruby code
30
+ # @param source_map [Hash] map of Ruby code line numbers to original line
31
+ # numbers in the template
32
+ def find_lints(ruby, source_map)
33
+ rubocop = ::RuboCop::CLI.new
34
+
35
+ filename = document.file ? "#{document.file}.rb" : "ruby_script.rb"
36
+
37
+ with_ruby_from_stdin(ruby) do
38
+ extract_lints_from_offenses(lint_file(rubocop, filename), source_map)
39
+ end
40
+ end
41
+
42
+ # Defined so we can stub the results in tests
43
+ #
44
+ # @param rubocop [RuboCop::CLI]
45
+ # @param file [String]
46
+ # @return [Array<RuboCop::Cop::Offense>]
47
+ def lint_file(rubocop, file)
48
+ rubocop.run(rubocop_flags << file)
49
+ OffenseCollector.offenses
50
+ end
51
+
52
+ # Aggregates RuboCop offenses and converts them to {SlimLint::Lint}s
53
+ # suitable for reporting.
54
+ #
55
+ # @param offenses [Array<RuboCop::Cop::Offense>]
56
+ # @param source_map [Hash]
57
+ def extract_lints_from_offenses(offenses, source_map)
58
+ offenses.reject! { |offense| config["ignored_cops"].include?(offense.cop_name) }
59
+ offenses.each do |offense|
60
+ @lints << Lint.new(
61
+ [self, offense.cop_name],
62
+ document.file,
63
+ location_for_line(source_map, offense),
64
+ offense.message.gsub(/ at \d+, \d+/, "")
65
+ )
66
+ end
67
+ end
68
+
69
+ # Returns flags that will be passed to RuboCop CLI.
70
+ #
71
+ # @return [Array<String>]
72
+ def rubocop_flags
73
+ flags = %w[--format SlimLint::Linter::RuboCop::OffenseCollector]
74
+ flags += ["--no-display-cop-names"]
75
+ flags += ["--config", ENV["SLIM_LINT_RUBOCOP_CONF"]] if ENV["SLIM_LINT_RUBOCOP_CONF"]
76
+ flags += ["--stdin"]
77
+ flags
78
+ end
79
+
80
+ # Overrides the global stdin to allow RuboCop to read Ruby code from it.
81
+ #
82
+ # @param ruby [String] the Ruby code to write to the overridden stdin
83
+ # @param _block [Block] the block to perform with the overridden stdin
84
+ # @return [void]
85
+ def with_ruby_from_stdin(ruby, &_block)
86
+ original_stdin = $stdin
87
+ stdin = StringIO.new
88
+ stdin.puts(ruby.chomp)
89
+ stdin.rewind
90
+ $stdin = stdin
91
+ yield
92
+ ensure
93
+ $stdin = original_stdin
94
+ end
95
+
96
+ def location_for_line(source_map, offense)
97
+ if source_map.key?(offense.line)
98
+ start = source_map[offense.line].adjust(column: offense.column)
99
+ finish = source_map[offense.last_line].adjust(column: offense.last_column)
100
+ SourceLocation.merge(start, finish, length: offense.column_length)
101
+ else
102
+ SourceLocation.new(start_line: document.source_lines.size, start_column: 0)
103
+ end
104
+ end
105
+
106
+ # Collects offenses detected by RuboCop.
107
+ class OffenseCollector < ::RuboCop::Formatter::BaseFormatter
108
+ class << self
109
+ # List of offenses reported by RuboCop.
110
+ attr_accessor :offenses
111
+ end
112
+
113
+ # Executed when RuboCop begins linting.
114
+ #
115
+ # @param _target_files [Array<String>]
116
+ def started(_target_files)
117
+ self.class.offenses = []
118
+ end
119
+
120
+ # Executed when a file has been scanned by RuboCop, adding the reported
121
+ # offenses to our collection.
122
+ #
123
+ # @param _file [String]
124
+ # @param offenses [Array<RuboCop::Cop::Offense>]
125
+ def file_finished(_file, offenses)
126
+ self.class.offenses += offenses
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end