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