lint_trappings 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/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
|