haml_lint 0.13.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/haml-lint +7 -0
- data/config/default.yml +91 -0
- data/lib/haml_lint/cli.rb +122 -0
- data/lib/haml_lint/configuration.rb +97 -0
- data/lib/haml_lint/configuration_loader.rb +68 -0
- data/lib/haml_lint/constants.rb +8 -0
- data/lib/haml_lint/exceptions.rb +15 -0
- data/lib/haml_lint/file_finder.rb +69 -0
- data/lib/haml_lint/haml_visitor.rb +36 -0
- data/lib/haml_lint/lint.rb +25 -0
- data/lib/haml_lint/linter/alt_text.rb +12 -0
- data/lib/haml_lint/linter/class_attribute_with_static_value.rb +51 -0
- data/lib/haml_lint/linter/classes_before_ids.rb +26 -0
- data/lib/haml_lint/linter/consecutive_comments.rb +20 -0
- data/lib/haml_lint/linter/consecutive_silent_scripts.rb +23 -0
- data/lib/haml_lint/linter/empty_script.rb +12 -0
- data/lib/haml_lint/linter/html_attributes.rb +14 -0
- data/lib/haml_lint/linter/implicit_div.rb +20 -0
- data/lib/haml_lint/linter/leading_comment_space.rb +14 -0
- data/lib/haml_lint/linter/line_length.rb +19 -0
- data/lib/haml_lint/linter/multiline_pipe.rb +43 -0
- data/lib/haml_lint/linter/multiline_script.rb +43 -0
- data/lib/haml_lint/linter/object_reference_attributes.rb +14 -0
- data/lib/haml_lint/linter/rubocop.rb +76 -0
- data/lib/haml_lint/linter/ruby_comments.rb +18 -0
- data/lib/haml_lint/linter/space_before_script.rb +52 -0
- data/lib/haml_lint/linter/space_inside_hash_attributes.rb +32 -0
- data/lib/haml_lint/linter/tag_name.rb +13 -0
- data/lib/haml_lint/linter/trailing_whitespace.rb +16 -0
- data/lib/haml_lint/linter/unnecessary_interpolation.rb +29 -0
- data/lib/haml_lint/linter/unnecessary_string_output.rb +39 -0
- data/lib/haml_lint/linter.rb +156 -0
- data/lib/haml_lint/linter_registry.rb +26 -0
- data/lib/haml_lint/logger.rb +107 -0
- data/lib/haml_lint/node_transformer.rb +28 -0
- data/lib/haml_lint/options.rb +89 -0
- data/lib/haml_lint/parser.rb +87 -0
- data/lib/haml_lint/rake_task.rb +107 -0
- data/lib/haml_lint/report.rb +16 -0
- data/lib/haml_lint/reporter/default_reporter.rb +39 -0
- data/lib/haml_lint/reporter/json_reporter.rb +44 -0
- data/lib/haml_lint/reporter.rb +36 -0
- data/lib/haml_lint/ruby_parser.rb +29 -0
- data/lib/haml_lint/runner.rb +76 -0
- data/lib/haml_lint/script_extractor.rb +181 -0
- data/lib/haml_lint/tree/comment_node.rb +5 -0
- data/lib/haml_lint/tree/doctype_node.rb +5 -0
- data/lib/haml_lint/tree/filter_node.rb +9 -0
- data/lib/haml_lint/tree/haml_comment_node.rb +18 -0
- data/lib/haml_lint/tree/node.rb +98 -0
- data/lib/haml_lint/tree/plain_node.rb +5 -0
- data/lib/haml_lint/tree/root_node.rb +5 -0
- data/lib/haml_lint/tree/script_node.rb +11 -0
- data/lib/haml_lint/tree/silent_script_node.rb +12 -0
- data/lib/haml_lint/tree/tag_node.rb +221 -0
- data/lib/haml_lint/utils.rb +58 -0
- data/lib/haml_lint/version.rb +4 -0
- data/lib/haml_lint.rb +36 -0
- metadata +175 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
module HamlLint
|
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 [HamlLint::Logger]
|
10
|
+
def self.silent
|
11
|
+
new(File.open('/dev/null', 'w'))
|
12
|
+
end
|
13
|
+
|
14
|
+
# Creates a new {HamlLint::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,28 @@
|
|
1
|
+
module HamlLint
|
2
|
+
# Responsible for transforming {Haml::Parser::ParseNode} objects into
|
3
|
+
# corresponding {HamlLint::Tree::Node} objects.
|
4
|
+
#
|
5
|
+
# The parse tree generated by HAML has a number of strange cases where certain
|
6
|
+
# types of nodes are created that don't necessarily correspond to what one
|
7
|
+
# would expect. This class is intended to isolate and handle these cases so
|
8
|
+
# that linters don't have to deal with them.
|
9
|
+
class NodeTransformer
|
10
|
+
# Creates a node transformer for the given parser context.
|
11
|
+
#
|
12
|
+
# @param parser [HamlLint::Parser]
|
13
|
+
def initialize(parser)
|
14
|
+
@parser = parser
|
15
|
+
end
|
16
|
+
|
17
|
+
# Transforms the given {Haml::Parser::ParseNode} into its corresponding
|
18
|
+
# {HamlLint::Tree::Node}.
|
19
|
+
def transform(haml_node)
|
20
|
+
node_class = "#{HamlLint::Utils.camel_case(haml_node.type.to_s)}Node"
|
21
|
+
|
22
|
+
HamlLint::Tree.const_get(node_class).new(@parser, haml_node)
|
23
|
+
rescue NameError
|
24
|
+
# TODO: Wrap in parser error?
|
25
|
+
raise
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module HamlLint
|
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] = HamlLint::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,87 @@
|
|
1
|
+
require 'haml'
|
2
|
+
|
3
|
+
module HamlLint
|
4
|
+
# Parses a HAML document for inspection by linters.
|
5
|
+
class Parser
|
6
|
+
attr_reader :contents, :filename, :lines, :tree
|
7
|
+
|
8
|
+
# Creates a parser containing the parse tree of a HAML document.
|
9
|
+
#
|
10
|
+
# @param haml_or_filename [String]
|
11
|
+
# @param options [Hash]
|
12
|
+
# @option options [true,false] 'skip_frontmatter' Whether to skip
|
13
|
+
# frontmatter included by frameworks such as Middleman or Jekyll
|
14
|
+
def initialize(haml_or_filename, options = {})
|
15
|
+
if File.exist?(haml_or_filename)
|
16
|
+
build_from_file(haml_or_filename)
|
17
|
+
else
|
18
|
+
build_from_string(haml_or_filename)
|
19
|
+
end
|
20
|
+
|
21
|
+
process_options(options)
|
22
|
+
|
23
|
+
build_parse_tree
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# @param path [String]
|
29
|
+
def build_from_file(path)
|
30
|
+
@filename = path
|
31
|
+
@contents = File.read(path)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param haml [String]
|
35
|
+
def build_from_string(haml)
|
36
|
+
@contents = haml
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_parse_tree
|
40
|
+
original_tree = Haml::Parser.new(@contents, Haml::Options.new).parse
|
41
|
+
|
42
|
+
# Remove the trailing empty HAML comment that the parser creates to signal
|
43
|
+
# the end of the HAML document
|
44
|
+
if Gem::Requirement.new('~> 4.0.0').satisfied_by?(Gem.loaded_specs['haml'].version)
|
45
|
+
original_tree.children.pop
|
46
|
+
end
|
47
|
+
|
48
|
+
@node_transformer = HamlLint::NodeTransformer.new(self)
|
49
|
+
@tree = convert_tree(original_tree)
|
50
|
+
end
|
51
|
+
|
52
|
+
def process_options(options)
|
53
|
+
if options['skip_frontmatter'] &&
|
54
|
+
@contents =~ /
|
55
|
+
# From the start of the string
|
56
|
+
\A
|
57
|
+
# First-capture match --- followed by optional whitespace up
|
58
|
+
# to a newline then 0 or more chars followed by an optional newline.
|
59
|
+
# This matches the --- and the contents of the frontmatter
|
60
|
+
(---\s*\n.*?\n?)
|
61
|
+
# From the start of the line
|
62
|
+
^
|
63
|
+
# Second capture match --- or ... followed by optional whitespace
|
64
|
+
# and newline. This matches the closing --- for the frontmatter.
|
65
|
+
(---|\.\.\.)\s*$\n?/mx
|
66
|
+
@contents = $POSTMATCH
|
67
|
+
end
|
68
|
+
|
69
|
+
@lines = @contents.split("\n")
|
70
|
+
end
|
71
|
+
|
72
|
+
# Converts a HAML parse tree to a tree of {HamlLint::Tree::Node} objects.
|
73
|
+
#
|
74
|
+
# This provides a cleaner interface with which the linters can interact with
|
75
|
+
# the parse tree.
|
76
|
+
def convert_tree(haml_node, parent = nil)
|
77
|
+
new_node = @node_transformer.transform(haml_node)
|
78
|
+
new_node.parent = parent
|
79
|
+
|
80
|
+
new_node.children = haml_node.children.map do |child|
|
81
|
+
convert_tree(child, new_node)
|
82
|
+
end
|
83
|
+
|
84
|
+
new_node
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
|
4
|
+
module HamlLint
|
5
|
+
# Rake task interface for haml-lint command line interface.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# # Add the following to your Rakefile...
|
9
|
+
# require 'haml_lint/rake_task'
|
10
|
+
#
|
11
|
+
# HamlLint::RakeTask.new do |t|
|
12
|
+
# t.config = 'path/to/custom/haml-lint.yml'
|
13
|
+
# t.files = %w[app/views/**/*.haml custom/*.haml]
|
14
|
+
# t.quiet = true # Don't display output from haml-lint
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # ...and then execute from the command line:
|
18
|
+
# rake haml_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 'haml_lint/rake_task'
|
25
|
+
#
|
26
|
+
# HamlLint::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 'haml_lint[app/views/**/*.haml, other_files/**/*.haml]'
|
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 haml-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 = :haml_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 'haml_lint'
|
72
|
+
require 'haml_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 ? HamlLint::Logger.silent : HamlLint::Logger.new(STDOUT)
|
82
|
+
result = HamlLint::CLI.new(logger).run(Array(cli_args) + files_to_lint(task_args))
|
83
|
+
|
84
|
+
fail "haml-lint 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 `haml-lint'
|
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 HamlLint
|
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 HamlLint
|
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 HamlLint
|
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
|
+
hamllint_version: 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 HamlLint
|
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 [HamlLint::Logger]
|
11
|
+
# @param report [HamlLint::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 {HamlLint::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,29 @@
|
|
1
|
+
require 'astrolabe/builder'
|
2
|
+
require 'parser/current'
|
3
|
+
|
4
|
+
module HamlLint
|
5
|
+
# Parser for the Ruby language.
|
6
|
+
#
|
7
|
+
# This provides a convenient wrapper around the `parser` gem and the
|
8
|
+
# `astrolabe` integration to go with it. It is intended to be used for linter
|
9
|
+
# checks that require deep inspection of Ruby code.
|
10
|
+
class RubyParser
|
11
|
+
# Creates a reusable parser.
|
12
|
+
def initialize
|
13
|
+
@builder = ::Astrolabe::Builder.new
|
14
|
+
@parser = ::Parser::CurrentRuby.new(@builder)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Parse the given Ruby source into an abstract syntax tree.
|
18
|
+
#
|
19
|
+
# @param source [String] Ruby source code
|
20
|
+
# @return [Array] syntax tree in the form returned by Parser gem
|
21
|
+
def parse(source)
|
22
|
+
buffer = ::Parser::Source::Buffer.new('(string)')
|
23
|
+
buffer.source = source
|
24
|
+
|
25
|
+
@parser.reset
|
26
|
+
@parser.parse(buffer)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module HamlLint
|
2
|
+
# Responsible for running the applicable linters against the desired files.
|
3
|
+
class Runner
|
4
|
+
# Make the list of applicable files available
|
5
|
+
attr_reader :files
|
6
|
+
|
7
|
+
# Runs the appropriate linters against the desired files given the specified
|
8
|
+
# options.
|
9
|
+
#
|
10
|
+
# @param options [Hash]
|
11
|
+
# @raise [HamlLint::Exceptions::NoLintersError] when no linters are enabled
|
12
|
+
# @return [HamlLint::Report] a summary of all lints found
|
13
|
+
def run(options = {})
|
14
|
+
config = load_applicable_config(options)
|
15
|
+
files = extract_applicable_files(options, config)
|
16
|
+
linters = extract_enabled_linters(config, options)
|
17
|
+
|
18
|
+
raise HamlLint::Exceptions::NoLintersError, 'No linters specified' if linters.empty?
|
19
|
+
|
20
|
+
@lints = []
|
21
|
+
files.each do |file|
|
22
|
+
find_lints(file, linters, config)
|
23
|
+
end
|
24
|
+
|
25
|
+
linters.each do |linter|
|
26
|
+
@lints += linter.lints
|
27
|
+
end
|
28
|
+
|
29
|
+
HamlLint::Report.new(@lints, files)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def load_applicable_config(options)
|
35
|
+
if options[:config_file]
|
36
|
+
HamlLint::ConfigurationLoader.load_file(options[:config_file])
|
37
|
+
else
|
38
|
+
HamlLint::ConfigurationLoader.load_applicable_config
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def extract_enabled_linters(config, options)
|
43
|
+
included_linters = LinterRegistry
|
44
|
+
.extract_linters_from(options.fetch(:included_linters, []))
|
45
|
+
|
46
|
+
included_linters = LinterRegistry.linters if included_linters.empty?
|
47
|
+
|
48
|
+
excluded_linters = LinterRegistry
|
49
|
+
.extract_linters_from(options.fetch(:excluded_linters, []))
|
50
|
+
|
51
|
+
# After filtering out explicitly included/excluded linters, only include
|
52
|
+
# linters which are enabled in the configuration
|
53
|
+
(included_linters - excluded_linters).map do |linter_class|
|
54
|
+
linter_config = config.for_linter(linter_class)
|
55
|
+
linter_class.new(linter_config) if linter_config['enabled']
|
56
|
+
end.compact
|
57
|
+
end
|
58
|
+
|
59
|
+
def find_lints(file, linters, config)
|
60
|
+
parser = Parser.new(file, config.hash)
|
61
|
+
|
62
|
+
linters.each do |linter|
|
63
|
+
linter.run(parser)
|
64
|
+
end
|
65
|
+
rescue Haml::Error => ex
|
66
|
+
@lints << Lint.new(nil, file, ex.line, ex.to_s, :error)
|
67
|
+
end
|
68
|
+
|
69
|
+
def extract_applicable_files(options, config)
|
70
|
+
included_patterns = options[:files]
|
71
|
+
excluded_files = options.fetch(:excluded_files, [])
|
72
|
+
|
73
|
+
HamlLint::FileFinder.new(config).find(included_patterns, excluded_files)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|