lint_trappings 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.md +21 -0
- data/lib/lint_trappings.rb +17 -0
- data/lib/lint_trappings/application.rb +138 -0
- data/lib/lint_trappings/arguments_parser.rb +145 -0
- data/lib/lint_trappings/cli.rb +61 -0
- data/lib/lint_trappings/command/base.rb +36 -0
- data/lib/lint_trappings/command/display_documentation.rb +65 -0
- data/lib/lint_trappings/command/display_formatters.rb +14 -0
- data/lib/lint_trappings/command/display_help.rb +8 -0
- data/lib/lint_trappings/command/display_linters.rb +24 -0
- data/lib/lint_trappings/command/display_version.rb +14 -0
- data/lib/lint_trappings/command/scan.rb +19 -0
- data/lib/lint_trappings/configuration.rb +94 -0
- data/lib/lint_trappings/configuration_loader.rb +98 -0
- data/lib/lint_trappings/configuration_resolver.rb +49 -0
- data/lib/lint_trappings/document.rb +45 -0
- data/lib/lint_trappings/errors.rb +127 -0
- data/lib/lint_trappings/executable.rb +26 -0
- data/lib/lint_trappings/file_finder.rb +171 -0
- data/lib/lint_trappings/formatter/base.rb +67 -0
- data/lib/lint_trappings/formatter/checkstyle.rb +34 -0
- data/lib/lint_trappings/formatter/default.rb +99 -0
- data/lib/lint_trappings/formatter/json.rb +62 -0
- data/lib/lint_trappings/formatter_forwarder.rb +23 -0
- data/lib/lint_trappings/formatter_loader.rb +45 -0
- data/lib/lint_trappings/lint.rb +37 -0
- data/lib/lint_trappings/linter.rb +182 -0
- data/lib/lint_trappings/linter_configuration_validator.rb +42 -0
- data/lib/lint_trappings/linter_loader.rb +44 -0
- data/lib/lint_trappings/linter_plugin.rb +35 -0
- data/lib/lint_trappings/linter_selector.rb +120 -0
- data/lib/lint_trappings/location.rb +39 -0
- data/lib/lint_trappings/output.rb +118 -0
- data/lib/lint_trappings/preprocessor.rb +41 -0
- data/lib/lint_trappings/rake_task.rb +145 -0
- data/lib/lint_trappings/report.rb +58 -0
- data/lib/lint_trappings/runner.rb +161 -0
- data/lib/lint_trappings/spec.rb +12 -0
- data/lib/lint_trappings/spec/directory_helpers.rb +22 -0
- data/lib/lint_trappings/spec/indentation_helpers.rb +7 -0
- data/lib/lint_trappings/spec/matchers/report_lint_matcher.rb +169 -0
- data/lib/lint_trappings/spec/shared_contexts/linter_shared_context.rb +35 -0
- data/lib/lint_trappings/utils.rb +123 -0
- data/lib/lint_trappings/version.rb +4 -0
- metadata +117 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
module LintTrappings
|
2
|
+
# Contains a collection of formatters and their output destinations, exposing
|
3
|
+
# them a single formatter.
|
4
|
+
#
|
5
|
+
# This quacks like a Formatter so that it can be used in place of a single
|
6
|
+
# formatter, but fans out the calls to all formatters in the collection.
|
7
|
+
class FormatterForwarder
|
8
|
+
def initialize(formatters)
|
9
|
+
@formatters = formatters
|
10
|
+
end
|
11
|
+
|
12
|
+
%i[
|
13
|
+
started
|
14
|
+
job_started
|
15
|
+
job_finished
|
16
|
+
finished
|
17
|
+
].each do |method_sym|
|
18
|
+
define_method method_sym do |*args|
|
19
|
+
@formatters.each { |formatter| formatter.send(method_sym, *args) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module LintTrappings
|
2
|
+
# Loads the configured formatters.
|
3
|
+
class FormatterLoader
|
4
|
+
def initialize(application, config, output)
|
5
|
+
@application = application
|
6
|
+
@config = config
|
7
|
+
@output = output
|
8
|
+
end
|
9
|
+
|
10
|
+
def load(options)
|
11
|
+
outputs = options.fetch(:formatters, [{ 'Default' => :stdout }])
|
12
|
+
|
13
|
+
outputs.map do |output_specification|
|
14
|
+
output_specification.map do |formatter_name, output_path|
|
15
|
+
load_formatter(formatter_name)
|
16
|
+
create_formatter(formatter_name, output_path, options)
|
17
|
+
end
|
18
|
+
end.flatten
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def load_formatter(formatter_name)
|
24
|
+
formatter_path = File.join('lint_trappings', 'formatter', Utils.snake_case(formatter_name))
|
25
|
+
require formatter_path
|
26
|
+
rescue LoadError, SyntaxError => ex
|
27
|
+
raise FormatterLoadError,
|
28
|
+
"Unable to load formatter `#{formatter_name}`: #{ex.message}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_formatter(formatter_name, output_path, options)
|
32
|
+
output_dest =
|
33
|
+
if output_path == :stdout
|
34
|
+
@output
|
35
|
+
else
|
36
|
+
Output.new(File.open(output_path, File::CREAT | File::WRONLY))
|
37
|
+
end
|
38
|
+
|
39
|
+
Formatter.const_get(formatter_name).new(@application, @config, options, output_dest)
|
40
|
+
rescue NameError => ex
|
41
|
+
raise FormatterLoadError,
|
42
|
+
"Unable to create formatter `#{formatter_name}`: #{ex.message}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module LintTrappings
|
2
|
+
# Contains information about a problem or issue with a document.
|
3
|
+
class Lint
|
4
|
+
# @return [LintTrappings::Linter, nil] linter that reported the lint (if applicable)
|
5
|
+
attr_reader :linter
|
6
|
+
|
7
|
+
# @return [String] file path to which the lint applies
|
8
|
+
attr_reader :path
|
9
|
+
|
10
|
+
# @return [Range<LintTrappings::Location>] source range of the problem within the file
|
11
|
+
attr_reader :source_range
|
12
|
+
|
13
|
+
# @return [String] message describing the lint
|
14
|
+
attr_reader :message
|
15
|
+
|
16
|
+
# @return [Symbol] whether this lint is a warning or an error
|
17
|
+
attr_reader :severity
|
18
|
+
|
19
|
+
# @return [Exception] the exception that was raised, if any
|
20
|
+
attr_reader :exception
|
21
|
+
|
22
|
+
# @param options [Hash]
|
23
|
+
# @option options :linter [LintTrappings::Linter] optional
|
24
|
+
# @option options :path [String]
|
25
|
+
# @option options :source_range [Range<LintTrappings::Location>]
|
26
|
+
# @option options :message [String]
|
27
|
+
# @option options :severity [Symbol]
|
28
|
+
def initialize(options)
|
29
|
+
@linter = options[:linter]
|
30
|
+
@path = options.fetch(:path)
|
31
|
+
@source_range = options.fetch(:source_range)
|
32
|
+
@message = options.fetch(:message)
|
33
|
+
@severity = options.fetch(:severity)
|
34
|
+
@exception = options[:exception]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'lint_trappings/linter_configuration_validator'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module LintTrappings
|
5
|
+
# Base implementation for all lint checks.
|
6
|
+
#
|
7
|
+
# @abstract
|
8
|
+
class Linter
|
9
|
+
class << self
|
10
|
+
# Return all subclasses.
|
11
|
+
#
|
12
|
+
# @return [Array<Class>]
|
13
|
+
def descendants
|
14
|
+
ObjectSpace.each_object(Class).select { |klass| klass < self }
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the canonical name for this linter class.
|
18
|
+
#
|
19
|
+
# The canonical name is used as the key for configuring the linter in the
|
20
|
+
# configuration file, or when referring to it from the command line.
|
21
|
+
#
|
22
|
+
# This uses the "Linter" module as an indicator of when to start removing
|
23
|
+
# unnecessary module prefixes.
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# LintTrappings::Linter::MyLinter
|
27
|
+
# => "MyLinter"
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
# MyCustomNamespace::MyLinter
|
31
|
+
# => "MyCustomNamespace::MyLinter"
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# MyModule::Linter::MyCustomNamespace::MyLinter
|
35
|
+
# => "MyCustomNamespace::MyLinter"
|
36
|
+
#
|
37
|
+
# @return [String]
|
38
|
+
def canonical_name
|
39
|
+
@canonical_name ||=
|
40
|
+
begin
|
41
|
+
full_name = name.to_s.split('::')
|
42
|
+
|
43
|
+
if linter_class_index = full_name.index('Linter')
|
44
|
+
# Otherwise, the name follows the `Linter` module
|
45
|
+
linter_class_index += 1
|
46
|
+
else
|
47
|
+
# If not found, include the full name
|
48
|
+
linter_class_index = 0
|
49
|
+
end
|
50
|
+
|
51
|
+
full_name[linter_class_index..-1].join('::')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def description(*args)
|
56
|
+
if args.any?
|
57
|
+
@description = args.first
|
58
|
+
else
|
59
|
+
@description
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def option(name, options)
|
64
|
+
options = options.dup
|
65
|
+
|
66
|
+
@options_spec ||= {}
|
67
|
+
opt = @options_spec[name] = {}
|
68
|
+
%i[type default description].each do |option_sym|
|
69
|
+
opt[option_sym] = options.delete(option_sym) if options[option_sym]
|
70
|
+
end
|
71
|
+
|
72
|
+
if options.keys.any?
|
73
|
+
raise InvalidOptionSpecificationError,
|
74
|
+
"Unknown key `#{options.keys.first}` for `#{name}` option " \
|
75
|
+
"specification on linter #{self}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def options
|
80
|
+
@options_spec || {}
|
81
|
+
end
|
82
|
+
|
83
|
+
attr_accessor :options_struct_class
|
84
|
+
end
|
85
|
+
|
86
|
+
# Initializes a linter with the specified configuration.
|
87
|
+
#
|
88
|
+
# @param config [Hash] configuration for this linter
|
89
|
+
def initialize(config)
|
90
|
+
@orig_hash_config = @config = config
|
91
|
+
validate_options_specification
|
92
|
+
@config = convert_config_hash_to_struct(@config)
|
93
|
+
@lints = []
|
94
|
+
end
|
95
|
+
|
96
|
+
# Runs the linter against the given Slim document.
|
97
|
+
#
|
98
|
+
# @param document [LintTrappings::Document]
|
99
|
+
def run(document)
|
100
|
+
@document = document
|
101
|
+
@lints = []
|
102
|
+
scan
|
103
|
+
@lints
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns the canonical name of this linter's class.
|
107
|
+
#
|
108
|
+
# @see {LintTrappings::Linter.canonical_name}
|
109
|
+
#
|
110
|
+
# @return [String]
|
111
|
+
def canonical_name
|
112
|
+
self.class.canonical_name
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
attr_reader :config, :document, :lints
|
118
|
+
|
119
|
+
# Scans the document for lints.
|
120
|
+
def scan
|
121
|
+
raise NotImplementedError, 'Subclass must implement #scan'
|
122
|
+
end
|
123
|
+
|
124
|
+
def validate_options_specification
|
125
|
+
LinterConfigurationValidator.new.validate(self, @config, self.class.options)
|
126
|
+
end
|
127
|
+
|
128
|
+
# List of built-in hook options which are available to every hook
|
129
|
+
BUILT_IN_HOOK_OPTIONS = %w[enabled severity include exclude]
|
130
|
+
|
131
|
+
# Converts a configuration hash to a struct so configuration values are
|
132
|
+
# accessed via method calls. This is valuable as it provides faster feedback
|
133
|
+
# in the event of a typo (you get an error instead of a `nil` value).
|
134
|
+
#
|
135
|
+
# @return [Struct]
|
136
|
+
def convert_config_hash_to_struct(hash)
|
137
|
+
option_names = self.class.options.keys
|
138
|
+
return OpenStruct.new unless option_names.any?
|
139
|
+
self.class.options_struct_class ||= Struct.new(*option_names)
|
140
|
+
|
141
|
+
unknown_keys = (hash.keys - option_names.map(&:to_s) - BUILT_IN_HOOK_OPTIONS)
|
142
|
+
if unknown_keys.any?
|
143
|
+
option_plural = Utils.pluralize('option', unknown_keys.count)
|
144
|
+
raise LinterConfigurationError,
|
145
|
+
"Unknown configuration #{option_plural} for #{canonical_name}: " \
|
146
|
+
"#{unknown_keys.join(', ')}\n" \
|
147
|
+
"Available options: #{(BUILT_IN_HOOK_OPTIONS + option_names).join(', ')}"
|
148
|
+
end
|
149
|
+
|
150
|
+
values = option_names.map { |option_name| hash[option_name.to_s] }
|
151
|
+
self.class.options_struct_class.new(*values)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Record a lint for reporting back to the user.
|
155
|
+
#
|
156
|
+
# @param range [Range<LintTrappings::Location>,#source_range] source range of lint
|
157
|
+
# @param message [String] error/warning to display to the user
|
158
|
+
def report_lint(range_or_obj, message)
|
159
|
+
unless range_or_obj.is_a?(Range) || range_or_obj.respond_to?(:source_range)
|
160
|
+
raise LinterError,
|
161
|
+
'`report_lint` must be given a Range or an object ' \
|
162
|
+
"that responds to `source_range`, but was given: #{range_or_obj.inspect}"
|
163
|
+
end
|
164
|
+
|
165
|
+
@lints << Lint.new(
|
166
|
+
linter: self,
|
167
|
+
path: @document.path,
|
168
|
+
source_range: range_or_obj.is_a?(Range) ? range_or_obj : range_or_obj.source_range,
|
169
|
+
message: message,
|
170
|
+
severity: @orig_hash_config.fetch('severity'),
|
171
|
+
)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Shortcut for creating a range for a single location.
|
175
|
+
#
|
176
|
+
# @return [Range<LintTrappings::Location>]
|
177
|
+
def location(*args)
|
178
|
+
loc = Location.new(*args)
|
179
|
+
loc..loc
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module LintTrappings
|
2
|
+
# Validates a linter's configuration options.
|
3
|
+
class LinterConfigurationValidator
|
4
|
+
# Verifies the configuration passed to this linter satisfies the options
|
5
|
+
# specifications declared in the linter class.
|
6
|
+
#
|
7
|
+
# @param linter [LintTrappings::Linter]
|
8
|
+
# @param config [Hash]
|
9
|
+
# @param options_specs [Hash]
|
10
|
+
def validate(linter, config, options_specs)
|
11
|
+
insert_default_values(config, options_specs)
|
12
|
+
check_option_types(linter, config, options_specs)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def check_option_types(linter, config, options_specs)
|
18
|
+
options_specs.select do |option_name, option_spec|
|
19
|
+
expected_class = option_spec[:type]
|
20
|
+
actual_value = config[option_name.to_s]
|
21
|
+
actual_class = actual_value.class
|
22
|
+
|
23
|
+
# If the class isn't the same or a subclass, it's different
|
24
|
+
next if actual_class <= expected_class
|
25
|
+
|
26
|
+
raise LinterConfigurationError,
|
27
|
+
"Option `#{option_name}` for linter " \
|
28
|
+
"#{linter.canonical_name} must be of " \
|
29
|
+
"type #{expected_class}, but was #{actual_class} (#{actual_value.inspect})!"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def insert_default_values(config, options_specs)
|
34
|
+
options_specs.select do |option_name, option_spec|
|
35
|
+
option_name_str = option_name.to_s
|
36
|
+
next unless option_spec.key?(:default) && !config.key?(option_name_str)
|
37
|
+
|
38
|
+
config[option_name_str] = option_spec[:default]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module LintTrappings
|
2
|
+
# Loads linters so they can be run.
|
3
|
+
class LinterLoader
|
4
|
+
def initialize(application, config)
|
5
|
+
@application = application
|
6
|
+
@config = config
|
7
|
+
end
|
8
|
+
|
9
|
+
# Load linters into memory so they can be instantiated.
|
10
|
+
#
|
11
|
+
# @param options [Hash]
|
12
|
+
#
|
13
|
+
# @raise [LinterLoadError] problem loading a linter file/library
|
14
|
+
def load(options)
|
15
|
+
load_directory(@application.linters_directory)
|
16
|
+
|
17
|
+
directories = Array(@config['linter_directories']) + Array(options[:linter_directories])
|
18
|
+
directories.each do |directory|
|
19
|
+
load_directory(directory)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Recursively load all files in a directory and its subdirectories.
|
26
|
+
def load_directory(directory)
|
27
|
+
# NOTE: While it might seem inefficient to load ALL linters (rather than
|
28
|
+
# only ones which are enabled), the reality is that the difference is
|
29
|
+
# negligible with respect to the application's startup time. It's also
|
30
|
+
# very difficult to do, as you can't infer the file name from the linter
|
31
|
+
# name (since the user can use any naming scheme they desire)
|
32
|
+
Dir[File.join(directory, '**', '*.rb')].each do |path|
|
33
|
+
load_path(path)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def load_path(path)
|
38
|
+
require path
|
39
|
+
rescue LoadError, SyntaxError => ex
|
40
|
+
raise LinterLoadError,
|
41
|
+
"Unable to load linter file '#{path}': #{ex.message}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module LintTrappings
|
2
|
+
# Represents a collection of linters/configuration which are loaded from a
|
3
|
+
# gem.
|
4
|
+
#
|
5
|
+
# This is just a wrapper to make accessing files in the gem easier.
|
6
|
+
class LinterPlugin
|
7
|
+
# @param require_path [String] name of the gem (must be the same as the path
|
8
|
+
# to `require`!)
|
9
|
+
def initialize(require_path)
|
10
|
+
@require_path = require_path
|
11
|
+
end
|
12
|
+
|
13
|
+
def load
|
14
|
+
require @require_path
|
15
|
+
rescue LoadError, SyntaxError => ex
|
16
|
+
raise LinterLoadError,
|
17
|
+
"Unable to load linter plugin at path '#{@require_path}': #{ex.message}"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns path to the configuration file that ships with this linter plugin.
|
21
|
+
#
|
22
|
+
# Note that this may not exist if no configuration is shipped with the gem.
|
23
|
+
#
|
24
|
+
# @return [String]
|
25
|
+
def config_file_path
|
26
|
+
File.join(gem_dir, 'config.yaml')
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def gem_dir
|
32
|
+
Gem::Specification.find_by_name(@require_path).gem_dir
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module LintTrappings
|
2
|
+
# Chooses the appropriate linters to run against a file.
|
3
|
+
#
|
4
|
+
# All linter inclusion/exclusion based on command line flags or configuration
|
5
|
+
# is handled here. This is utilized by the runner to generate linter/file
|
6
|
+
# tuples representing jobs to execute (i.e. run the linter X against file Y).
|
7
|
+
class LinterSelector
|
8
|
+
# @param application [LintTrappings::Application]
|
9
|
+
# @param config [LintTrappings::Configuration]
|
10
|
+
# @param options [Hash]
|
11
|
+
#
|
12
|
+
# @raise [LintTrappings::NoLintersError] when no linters are enabled
|
13
|
+
def initialize(application, config, options)
|
14
|
+
@application = application
|
15
|
+
@config = config
|
16
|
+
@options = options
|
17
|
+
|
18
|
+
# Pre-compute this as it is expensive to calculate and used many times.
|
19
|
+
# This forces any errors in the configuration to be surfaced ahead of time.
|
20
|
+
@enabled_linter_classes = enabled_linter_classes
|
21
|
+
end
|
22
|
+
|
23
|
+
# Return all loaded linter classes for this application.
|
24
|
+
# @return [Array<Class>]
|
25
|
+
def all_linter_classes
|
26
|
+
@application.linter_base_class.descendants
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns initialized linter instances to run against a given file.
|
30
|
+
#
|
31
|
+
# @param path [String]
|
32
|
+
#
|
33
|
+
# @return [Array<LintTrappings::Linter>]
|
34
|
+
def linters_for_file(path)
|
35
|
+
@enabled_linter_classes.map do |linter_class|
|
36
|
+
linter_conf = @config.for_linter(linter_class)
|
37
|
+
next unless run_linter_on_file?(linter_conf, path)
|
38
|
+
|
39
|
+
linter_class.new(linter_conf)
|
40
|
+
end.compact
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns a list of linters that are enabled given the specified
|
44
|
+
# configuration and additional options.
|
45
|
+
#
|
46
|
+
# @return [Array<LintTrappings::Linter>]
|
47
|
+
def enabled_linter_classes
|
48
|
+
# Include the explicit list of linters if a list was specified
|
49
|
+
explicitly_included = included_linter_classes =
|
50
|
+
linter_classes_from_names(@options.fetch(:included_linters, []))
|
51
|
+
|
52
|
+
if included_linter_classes.empty?
|
53
|
+
# Otherwise use the list of enabled linters specified by the config.
|
54
|
+
# Note: this means that a linter which is disabled in the configuration
|
55
|
+
# can still be run if it is explicitly specified in `included_linters`
|
56
|
+
included_linter_classes = all_linter_classes.select do |linter_class|
|
57
|
+
linter_enabled?(linter_class)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
excluded_linter_classes =
|
62
|
+
linter_classes_from_names(@options.fetch(:excluded_linters, []))
|
63
|
+
|
64
|
+
linter_classes = included_linter_classes - excluded_linter_classes
|
65
|
+
|
66
|
+
# Highlight conditions where all linters were filtered out, as this was
|
67
|
+
# likely a mistake on the user's part
|
68
|
+
if linter_classes.empty?
|
69
|
+
if explicitly_included.any?
|
70
|
+
raise NoLintersError,
|
71
|
+
'All specified linters were explicitly excluded!'
|
72
|
+
elsif included_linter_classes.empty?
|
73
|
+
raise NoLintersError,
|
74
|
+
'All linters are disabled. Enable some in your configuration!'
|
75
|
+
else
|
76
|
+
raise NoLintersError,
|
77
|
+
'All enabled linters were explicitly excluded!'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
linter_classes
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# Whether to run the given linter against the specified file.
|
87
|
+
#
|
88
|
+
# @param linter_conf [Hash]
|
89
|
+
# @param path [String]
|
90
|
+
#
|
91
|
+
# @return [Boolean]
|
92
|
+
def run_linter_on_file?(linter_conf, path)
|
93
|
+
if linter_conf['include'] &&
|
94
|
+
!Utils.any_glob_matches?(linter_conf['include'], path)
|
95
|
+
return false
|
96
|
+
end
|
97
|
+
|
98
|
+
if Utils.any_glob_matches?(linter_conf['exclude'], path)
|
99
|
+
return false
|
100
|
+
end
|
101
|
+
|
102
|
+
true
|
103
|
+
end
|
104
|
+
|
105
|
+
def linter_enabled?(linter_class)
|
106
|
+
@config.for_linter(linter_class)['enabled']
|
107
|
+
end
|
108
|
+
|
109
|
+
def linter_classes_from_names(linter_names)
|
110
|
+
linter_names.map do |linter_name|
|
111
|
+
begin
|
112
|
+
@application.linter_base_class.const_get(linter_name)
|
113
|
+
rescue NameError
|
114
|
+
raise NoSuchLinter,
|
115
|
+
"Linter #{linter_name} does not exist! Are you sure you spelt it correctly?"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|