slim_lint 0.1.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 +7 -0
- data/bin/slim-lint +7 -0
- data/config/default.yml +38 -0
- data/lib/slim_lint/cli.rb +122 -0
- data/lib/slim_lint/configuration.rb +101 -0
- data/lib/slim_lint/configuration_loader.rb +68 -0
- data/lib/slim_lint/constants.rb +8 -0
- data/lib/slim_lint/document.rb +52 -0
- data/lib/slim_lint/engine.rb +27 -0
- data/lib/slim_lint/exceptions.rb +15 -0
- data/lib/slim_lint/file_finder.rb +69 -0
- data/lib/slim_lint/filters/inject_line_numbers.rb +35 -0
- data/lib/slim_lint/filters/sexp_converter.rb +11 -0
- data/lib/slim_lint/lint.rb +25 -0
- data/lib/slim_lint/linter/line_length.rb +19 -0
- data/lib/slim_lint/linter/redundant_div.rb +17 -0
- data/lib/slim_lint/linter/rubocop.rb +73 -0
- data/lib/slim_lint/linter/trailing_whitespace.rb +17 -0
- data/lib/slim_lint/linter.rb +49 -0
- data/lib/slim_lint/linter_registry.rb +26 -0
- data/lib/slim_lint/logger.rb +107 -0
- data/lib/slim_lint/options.rb +89 -0
- data/lib/slim_lint/rake_task.rb +107 -0
- data/lib/slim_lint/report.rb +16 -0
- data/lib/slim_lint/reporter/default_reporter.rb +39 -0
- data/lib/slim_lint/reporter/json_reporter.rb +44 -0
- data/lib/slim_lint/reporter.rb +36 -0
- data/lib/slim_lint/ruby_extract_engine.rb +43 -0
- data/lib/slim_lint/ruby_extractor.rb +91 -0
- data/lib/slim_lint/ruby_parser.rb +29 -0
- data/lib/slim_lint/runner.rb +72 -0
- data/lib/slim_lint/sexp.rb +90 -0
- data/lib/slim_lint/sexp_visitor.rb +105 -0
- data/lib/slim_lint/version.rb +4 -0
- data/lib/slim_lint.rb +40 -0
- metadata +149 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'slim_lint/ruby_extractor'
|
2
|
+
require 'slim_lint/ruby_extract_engine'
|
3
|
+
require 'rubocop'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
module SlimLint
|
7
|
+
# Runs RuboCop on Ruby code extracted from Slim templates.
|
8
|
+
class Linter::RuboCop < Linter
|
9
|
+
include LinterRegistry
|
10
|
+
|
11
|
+
on_start do |_sexp|
|
12
|
+
processed_sexp = SlimLint::RubyExtractEngine.new.call(document.source)
|
13
|
+
|
14
|
+
extractor = SlimLint::RubyExtractor.new
|
15
|
+
extracted_ruby = extractor.extract(processed_sexp)
|
16
|
+
|
17
|
+
find_lints(extractor, extracted_ruby) unless extracted_ruby.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def find_lints(extractor, ruby)
|
23
|
+
rubocop = ::RuboCop::CLI.new
|
24
|
+
|
25
|
+
original_filename = document.file || 'ruby_script'
|
26
|
+
filename = "#{File.basename(original_filename)}.slim_lint.tmp"
|
27
|
+
directory = File.dirname(original_filename)
|
28
|
+
|
29
|
+
Tempfile.open(filename, directory) do |f|
|
30
|
+
begin
|
31
|
+
f.write(ruby)
|
32
|
+
f.close
|
33
|
+
extract_lints_from_offences(lint_file(rubocop, f.path), extractor)
|
34
|
+
ensure
|
35
|
+
f.unlink
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Defined so we can stub the results in tests
|
41
|
+
def lint_file(rubocop, file)
|
42
|
+
rubocop.run(%w[--format SlimLint::OffenceCollector] << file)
|
43
|
+
OffenceCollector.offences
|
44
|
+
end
|
45
|
+
|
46
|
+
def extract_lints_from_offences(offences, extractor)
|
47
|
+
offences.select { |offence| !config['ignored_cops'].include?(offence.cop_name) }
|
48
|
+
.each do |offence|
|
49
|
+
@lints << Lint.new(self,
|
50
|
+
document.file,
|
51
|
+
extractor.source_map[offence.line],
|
52
|
+
"#{offence.cop_name}: #{offence.message}")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Collects offences detected by RuboCop.
|
58
|
+
class OffenceCollector < ::RuboCop::Formatter::BaseFormatter
|
59
|
+
attr_accessor :offences
|
60
|
+
|
61
|
+
class << self
|
62
|
+
attr_accessor :offences
|
63
|
+
end
|
64
|
+
|
65
|
+
def started(_target_files)
|
66
|
+
self.class.offences = []
|
67
|
+
end
|
68
|
+
|
69
|
+
def file_finished(_file, offences)
|
70
|
+
self.class.offences += offences
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SlimLint
|
2
|
+
# Checks for trailing whitespace.
|
3
|
+
class Linter::TrailingWhitespace < Linter
|
4
|
+
include LinterRegistry
|
5
|
+
|
6
|
+
on_start do |_sexp|
|
7
|
+
dummy_node = Struct.new(:line)
|
8
|
+
|
9
|
+
document.source_lines.each_with_index do |line, index|
|
10
|
+
next unless line =~ /\s+$/
|
11
|
+
|
12
|
+
report_lint(dummy_node.new(index + 1),
|
13
|
+
'Line contains trailing whitespace')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module SlimLint
|
2
|
+
# Base implementation for all lint checks.
|
3
|
+
class Linter
|
4
|
+
# Include definitions for Sexp pattern-matching helpers.
|
5
|
+
include SexpVisitor
|
6
|
+
extend SexpVisitor::DSL
|
7
|
+
|
8
|
+
# TODO: Remove once spec support returns an array of lints instead of a
|
9
|
+
# linter
|
10
|
+
attr_reader :lints
|
11
|
+
|
12
|
+
# @param config [Hash] configuration for this linter
|
13
|
+
def initialize(config)
|
14
|
+
@config = config
|
15
|
+
@lints = []
|
16
|
+
@ruby_parser = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# Runs the linter against the specified Sexp
|
20
|
+
def run(document)
|
21
|
+
@document = document
|
22
|
+
@lints = []
|
23
|
+
trigger_pattern_callbacks(document.sexp)
|
24
|
+
@lints
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the simple name for this linter.
|
28
|
+
def name
|
29
|
+
self.class.name.split('::').last
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :config, :document
|
35
|
+
|
36
|
+
# Record a lint for reporting back to the user.
|
37
|
+
def report_lint(sexp, message)
|
38
|
+
@lints << SlimLint::Lint.new(self, @document.file, sexp.line, message)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Parse Ruby code into an abstract syntax tree.
|
42
|
+
#
|
43
|
+
# @return [AST::Node]
|
44
|
+
def parse_ruby(source)
|
45
|
+
@ruby_parser ||= SlimLint::RubyParser.new
|
46
|
+
@ruby_parser.parse(source)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module SlimLint
|
2
|
+
class NoSuchLinter < StandardError; end
|
3
|
+
|
4
|
+
# Stores all defined linters.
|
5
|
+
module LinterRegistry
|
6
|
+
@linters = []
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_reader :linters
|
10
|
+
|
11
|
+
def included(base)
|
12
|
+
@linters << base
|
13
|
+
end
|
14
|
+
|
15
|
+
def extract_linters_from(linter_names)
|
16
|
+
linter_names.map do |linter_name|
|
17
|
+
begin
|
18
|
+
SlimLint::Linter.const_get(linter_name)
|
19
|
+
rescue NameError
|
20
|
+
raise NoSuchLinter, "Linter #{linter_name} does not exist"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module SlimLint
|
2
|
+
# Encapsulates all communication to an output source.
|
3
|
+
class Logger
|
4
|
+
# Whether colored output via ANSI escape sequences is enabled.
|
5
|
+
# @return [true,false]
|
6
|
+
attr_accessor :color_enabled
|
7
|
+
|
8
|
+
# Creates a logger which outputs nothing.
|
9
|
+
# @return [SlimLint::Logger]
|
10
|
+
def self.silent
|
11
|
+
new(File.open('/dev/null', 'w'))
|
12
|
+
end
|
13
|
+
|
14
|
+
# Creates a new {SlimLint::Logger} instance.
|
15
|
+
#
|
16
|
+
# @param out [IO] the output destination.
|
17
|
+
def initialize(out)
|
18
|
+
@out = out
|
19
|
+
end
|
20
|
+
|
21
|
+
# Print the specified output.
|
22
|
+
#
|
23
|
+
# @param output [String] the output to send
|
24
|
+
# @param newline [true,false] whether to append a newline
|
25
|
+
# @return [nil]
|
26
|
+
def log(output, newline = true)
|
27
|
+
@out.print(output)
|
28
|
+
@out.print("\n") if newline
|
29
|
+
end
|
30
|
+
|
31
|
+
# Print the specified output in bold face.
|
32
|
+
# If output destination is not a TTY, behaves the same as {#log}.
|
33
|
+
#
|
34
|
+
# @param args [Array<String>]
|
35
|
+
# @return [nil]
|
36
|
+
def bold(*args)
|
37
|
+
color('1', *args)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Print the specified output in a color indicative of error.
|
41
|
+
# If output destination is not a TTY, behaves the same as {#log}.
|
42
|
+
#
|
43
|
+
# @param args [Array<String>]
|
44
|
+
# @return [nil]
|
45
|
+
def error(*args)
|
46
|
+
color(31, *args)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Print the specified output in a bold face and color indicative of error.
|
50
|
+
# If output destination is not a TTY, behaves the same as {#log}.
|
51
|
+
#
|
52
|
+
# @param args [Array<String>]
|
53
|
+
# @return [nil]
|
54
|
+
def bold_error(*args)
|
55
|
+
color('1;31', *args)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Print the specified output in a color indicative of success.
|
59
|
+
# If output destination is not a TTY, behaves the same as {#log}.
|
60
|
+
#
|
61
|
+
# @param args [Array<String>]
|
62
|
+
# @return [nil]
|
63
|
+
def success(*args)
|
64
|
+
color(32, *args)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Print the specified output in a color indicative of a warning.
|
68
|
+
# If output destination is not a TTY, behaves the same as {#log}.
|
69
|
+
#
|
70
|
+
# @param args [Array<String>]
|
71
|
+
# @return [nil]
|
72
|
+
def warning(*args)
|
73
|
+
color(33, *args)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Print specified output in bold face in a color indicative of a warning.
|
77
|
+
# If output destination is not a TTY, behaves the same as {#log}.
|
78
|
+
#
|
79
|
+
# @param args [Array<String>]
|
80
|
+
# @return [nil]
|
81
|
+
def bold_warning(*args)
|
82
|
+
color('1;33', *args)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Print the specified output in a color indicating information.
|
86
|
+
# If output destination is not a TTY, behaves the same as {#log}.
|
87
|
+
#
|
88
|
+
# @param args [Array<String>]
|
89
|
+
# @return [nil]
|
90
|
+
def info(*args)
|
91
|
+
color(36, *args)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Whether this logger is outputting to a TTY.
|
95
|
+
#
|
96
|
+
# @return [true,false]
|
97
|
+
def tty?
|
98
|
+
@out.respond_to?(:tty?) && @out.tty?
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def color(code, output, newline = true)
|
104
|
+
log(color_enabled ? "\033[#{code}m#{output}\033[0m" : output, newline)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module SlimLint
|
4
|
+
# Handles option parsing for the command line application.
|
5
|
+
class Options
|
6
|
+
# Parses command line options into an options hash.
|
7
|
+
#
|
8
|
+
# @param args [Array<String>] arguments passed via the command line
|
9
|
+
# @return [Hash] parsed options
|
10
|
+
def parse(args)
|
11
|
+
@options = {}
|
12
|
+
|
13
|
+
OptionParser.new do |parser|
|
14
|
+
parser.banner = "Usage: #{APP_NAME} [options] [file1, file2, ...]"
|
15
|
+
|
16
|
+
add_linter_options parser
|
17
|
+
add_file_options parser
|
18
|
+
add_info_options parser
|
19
|
+
end.parse!(args)
|
20
|
+
|
21
|
+
# Any remaining arguments are assumed to be files
|
22
|
+
@options[:files] = args
|
23
|
+
|
24
|
+
@options
|
25
|
+
rescue OptionParser::InvalidOption => ex
|
26
|
+
raise Exceptions::InvalidCLIOption,
|
27
|
+
ex.message,
|
28
|
+
ex.backtrace
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def add_linter_options(parser)
|
34
|
+
parser.on('-e', '--exclude file,...', Array,
|
35
|
+
'List of file names to exclude') do |files|
|
36
|
+
@options[:excluded_files] = files
|
37
|
+
end
|
38
|
+
|
39
|
+
parser.on('-i', '--include-linter linter,...', Array,
|
40
|
+
'Specify which linters you want to run') do |linters|
|
41
|
+
@options[:included_linters] = linters
|
42
|
+
end
|
43
|
+
|
44
|
+
parser.on('-x', '--exclude-linter linter,...', Array,
|
45
|
+
"Specify which linters you don't want to run") do |linters|
|
46
|
+
@options[:excluded_linters] = linters
|
47
|
+
end
|
48
|
+
|
49
|
+
parser.on('-r', '--reporter reporter', String,
|
50
|
+
'Specify which reporter you want to use to generate the output') do |reporter|
|
51
|
+
@options[:reporter] = SlimLint::Reporter.const_get("#{reporter.capitalize}Reporter")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_file_options(parser)
|
56
|
+
parser.on('-c', '--config config-file', String,
|
57
|
+
'Specify which configuration file you want to use') do |conf_file|
|
58
|
+
@options[:config_file] = conf_file
|
59
|
+
end
|
60
|
+
|
61
|
+
parser.on('-e', '--exclude file,...', Array,
|
62
|
+
'List of file names to exclude') do |files|
|
63
|
+
@options[:excluded_files] = files
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_info_options(parser)
|
68
|
+
parser.on('--show-linters', 'Display available linters') do
|
69
|
+
@options[:show_linters] = true
|
70
|
+
end
|
71
|
+
|
72
|
+
parser.on('--show-reporters', 'Display available reporters') do
|
73
|
+
@options[:show_reporters] = true
|
74
|
+
end
|
75
|
+
|
76
|
+
parser.on('--[no-]color', 'Force output to be colorized') do |color|
|
77
|
+
@options[:color] = color
|
78
|
+
end
|
79
|
+
|
80
|
+
parser.on_tail('-h', '--help', 'Display help documentation') do
|
81
|
+
@options[:help] = parser.help
|
82
|
+
end
|
83
|
+
|
84
|
+
parser.on_tail('-v', '--version', 'Display version') do
|
85
|
+
@options[:version] = true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
|
4
|
+
module SlimLint
|
5
|
+
# Rake task interface for slim-lint command line interface.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# # Add the following to your Rakefile...
|
9
|
+
# require 'slim_lint/rake_task'
|
10
|
+
#
|
11
|
+
# SlimLint::RakeTask.new do |t|
|
12
|
+
# t.config = 'path/to/custom/slim-lint.yml'
|
13
|
+
# t.files = %w[app/views/**/*.slim custom/*.slim]
|
14
|
+
# t.quiet = true # Don't display output from slim-lint
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # ...and then execute from the command line:
|
18
|
+
# rake slim_lint
|
19
|
+
#
|
20
|
+
# You can also specify the list of files as explicit task arguments:
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# # Add the following to your Rakefile...
|
24
|
+
# require 'slim_lint/rake_task'
|
25
|
+
#
|
26
|
+
# SlimLint::RakeTask.new
|
27
|
+
#
|
28
|
+
# # ...and then execute from the command line (single quotes prevent shell
|
29
|
+
# # glob expansion and allow us to have a space after commas):
|
30
|
+
# rake 'slim_lint[app/views/**/*.slim, other_files/**/*.slim]'
|
31
|
+
#
|
32
|
+
class RakeTask < Rake::TaskLib
|
33
|
+
# Name of the task.
|
34
|
+
# @return [String]
|
35
|
+
attr_accessor :name
|
36
|
+
|
37
|
+
# Configuration file to use.
|
38
|
+
# @return [String]
|
39
|
+
attr_accessor :config
|
40
|
+
|
41
|
+
# List of files to lint (can contain shell globs).
|
42
|
+
#
|
43
|
+
# Note that this will be ignored if you explicitly pass a list of files as
|
44
|
+
# task arguments via the command line or a task definition.
|
45
|
+
# @return [Array<String>]
|
46
|
+
attr_accessor :files
|
47
|
+
|
48
|
+
# Whether output from slim-lint should not be displayed to the standard out
|
49
|
+
# stream.
|
50
|
+
# @return [true,false]
|
51
|
+
attr_accessor :quiet
|
52
|
+
|
53
|
+
# Create the task so it exists in the current namespace.
|
54
|
+
def initialize(name = :slim_lint)
|
55
|
+
@name = name
|
56
|
+
@files = ['.'] # Search for everything under current directory by default
|
57
|
+
@quiet = false
|
58
|
+
|
59
|
+
yield self if block_given?
|
60
|
+
|
61
|
+
define
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def define
|
67
|
+
desc default_description unless ::Rake.application.last_description
|
68
|
+
|
69
|
+
task(name, [:files]) do |_task, task_args|
|
70
|
+
# Lazy-load so task doesn't affect Rakefile load time
|
71
|
+
require 'slim_lint'
|
72
|
+
require 'slim_lint/cli'
|
73
|
+
|
74
|
+
run_cli(task_args)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def run_cli(task_args)
|
79
|
+
cli_args = ['--config', config] if config
|
80
|
+
|
81
|
+
logger = quiet ? SlimLint::Logger.silent : SlimLint::Logger.new(STDOUT)
|
82
|
+
result = SlimLint::CLI.new(logger).run(Array(cli_args) + files_to_lint(task_args))
|
83
|
+
|
84
|
+
fail "#{SlimLint::APP_NAME} failed with exit code #{result}" unless result == 0
|
85
|
+
end
|
86
|
+
|
87
|
+
def files_to_lint(task_args)
|
88
|
+
# Note: we're abusing Rake's argument handling a bit here. We call the
|
89
|
+
# first argument `files` but it's actually only the first file--we pull
|
90
|
+
# the rest out of the `extras` from the task arguments. This is so we
|
91
|
+
# can specify an arbitrary list of files separated by commas on the
|
92
|
+
# command line or in a custom task definition.
|
93
|
+
explicit_files = Array(task_args[:files]) + Array(task_args.extras)
|
94
|
+
|
95
|
+
explicit_files.any? ? explicit_files : files
|
96
|
+
end
|
97
|
+
|
98
|
+
# Friendly description that shows the full command that will be executed.
|
99
|
+
def default_description
|
100
|
+
description = "Run `#{SlimLint::APP_NAME}"
|
101
|
+
description += " --config #{config}" if config
|
102
|
+
description += " #{files.join(' ')}" if files.any?
|
103
|
+
description += ' [files...]`'
|
104
|
+
description
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module SlimLint
|
2
|
+
# Contains information about all lints detected during a scan.
|
3
|
+
class Report
|
4
|
+
attr_accessor :lints
|
5
|
+
attr_reader :files
|
6
|
+
|
7
|
+
def initialize(lints, files)
|
8
|
+
@lints = lints.sort_by { |l| [l.filename, l.line] }
|
9
|
+
@files = files
|
10
|
+
end
|
11
|
+
|
12
|
+
def failed?
|
13
|
+
@lints.any?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module SlimLint
|
2
|
+
# Outputs lints in a simple format with the filename, line number, and lint
|
3
|
+
# message.
|
4
|
+
class Reporter::DefaultReporter < Reporter
|
5
|
+
def report_lints
|
6
|
+
sorted_lints = lints.sort_by { |l| [l.filename, l.line] }
|
7
|
+
|
8
|
+
sorted_lints.each do |lint|
|
9
|
+
print_location(lint)
|
10
|
+
print_type(lint)
|
11
|
+
print_message(lint)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def print_location(lint)
|
18
|
+
log.info lint.filename, false
|
19
|
+
log.log ':', false
|
20
|
+
log.bold lint.line, false
|
21
|
+
end
|
22
|
+
|
23
|
+
def print_type(lint)
|
24
|
+
if lint.error?
|
25
|
+
log.error ' [E] ', false
|
26
|
+
else
|
27
|
+
log.warning ' [W] ', false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def print_message(lint)
|
32
|
+
if lint.linter
|
33
|
+
log.success("#{lint.linter.name}: ", false)
|
34
|
+
end
|
35
|
+
|
36
|
+
log.log lint.message
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module SlimLint
|
2
|
+
# Outputs report as a JSON document.
|
3
|
+
class Reporter::JsonReporter < Reporter
|
4
|
+
def report_lints
|
5
|
+
grouped = lints.group_by(&:filename)
|
6
|
+
|
7
|
+
report = {
|
8
|
+
metadata: {
|
9
|
+
slim_lint_version: SlimLint::VERSION,
|
10
|
+
ruby_engine: RUBY_ENGINE,
|
11
|
+
ruby_patchlevel: RUBY_PATCHLEVEL.to_s,
|
12
|
+
ruby_platform: RUBY_PLATFORM,
|
13
|
+
},
|
14
|
+
files: grouped.map { |l| map_file(l) },
|
15
|
+
summary: {
|
16
|
+
offense_count: lints.length,
|
17
|
+
target_file_count: grouped.length,
|
18
|
+
inspected_file_count: files.length,
|
19
|
+
},
|
20
|
+
}
|
21
|
+
|
22
|
+
log.log report.to_json
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def map_file(file)
|
28
|
+
{
|
29
|
+
path: file.first,
|
30
|
+
offenses: file.last.map { |o| map_offense(o) },
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def map_offense(offense)
|
35
|
+
{
|
36
|
+
severity: offense.severity,
|
37
|
+
message: offense.message,
|
38
|
+
location: {
|
39
|
+
line: offense.line,
|
40
|
+
},
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module SlimLint
|
2
|
+
# Abstract lint reporter. Subclass and override {#report_lints} to
|
3
|
+
# implement a custom lint reporter.
|
4
|
+
#
|
5
|
+
# @abstract
|
6
|
+
class Reporter
|
7
|
+
attr_reader :lints
|
8
|
+
attr_reader :files
|
9
|
+
|
10
|
+
# @param logger [SlimLint::Logger]
|
11
|
+
# @param report [SlimLint::Report]
|
12
|
+
def initialize(logger, report)
|
13
|
+
@log = logger
|
14
|
+
@lints = report.lints
|
15
|
+
@files = report.files
|
16
|
+
end
|
17
|
+
|
18
|
+
# Implemented by subclasses to display lints from a {SlimLint::Report}.
|
19
|
+
def report_lints
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
# Keep tracking all the descendants of this class for the list of available reporters
|
24
|
+
def self.descendants
|
25
|
+
@descendants ||= []
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.inherited(descendant)
|
29
|
+
descendants << descendant
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :log
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module SlimLint
|
2
|
+
# Generates a {SlimLint::Sexp} suitable for consumption by the
|
3
|
+
# {RubyExtractor}.
|
4
|
+
#
|
5
|
+
# This is mostly copied from Slim::Engine, with some filters and generators
|
6
|
+
# omitted.
|
7
|
+
class RubyExtractEngine < Temple::Engine
|
8
|
+
define_options sort_attrs: true,
|
9
|
+
format: :xhtml,
|
10
|
+
attr_quote: '"',
|
11
|
+
merge_attrs: { 'class' => ' ' },
|
12
|
+
default_tag: 'div'
|
13
|
+
|
14
|
+
filter :Encoding
|
15
|
+
filter :RemoveBOM
|
16
|
+
|
17
|
+
# Parse into S-expression using Slim parser
|
18
|
+
use Slim::Parser
|
19
|
+
|
20
|
+
# Perform additional processing so extracting Ruby code in {RubyExtractor}
|
21
|
+
# is easier. We don't do this for regular linters because some information
|
22
|
+
# about the original syntax tree is lost in the process, but that doesn't
|
23
|
+
# matter in this case.
|
24
|
+
use Slim::Embedded
|
25
|
+
use Slim::Interpolation
|
26
|
+
use Slim::Splat::Filter
|
27
|
+
use Slim::DoInserter
|
28
|
+
use Slim::EndInserter
|
29
|
+
use Slim::Controls
|
30
|
+
html :AttributeSorter
|
31
|
+
html :AttributeMerger
|
32
|
+
use Slim::CodeAttributes
|
33
|
+
filter :ControlFlow
|
34
|
+
filter :MultiFlattener
|
35
|
+
filter :StaticMerger
|
36
|
+
|
37
|
+
# Converts Array-based S-expressions into SlimLint::Sexp objects, and gives
|
38
|
+
# them line numbers so we can easily map from the Ruby source to the
|
39
|
+
# original source
|
40
|
+
use SlimLint::Filters::SexpConverter
|
41
|
+
use SlimLint::Filters::InjectLineNumbers
|
42
|
+
end
|
43
|
+
end
|