slim_lint_standard 0.0.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/bin/slim-lint-standard +7 -0
- data/config/default.yml +109 -0
- data/lib/slim_lint/atom.rb +129 -0
- data/lib/slim_lint/capture_map.rb +19 -0
- data/lib/slim_lint/cli.rb +167 -0
- data/lib/slim_lint/configuration.rb +111 -0
- data/lib/slim_lint/configuration_loader.rb +86 -0
- data/lib/slim_lint/constants.rb +10 -0
- data/lib/slim_lint/document.rb +78 -0
- data/lib/slim_lint/engine.rb +41 -0
- data/lib/slim_lint/exceptions.rb +20 -0
- data/lib/slim_lint/file_finder.rb +88 -0
- data/lib/slim_lint/filter.rb +126 -0
- data/lib/slim_lint/filters/attribute_processor.rb +46 -0
- data/lib/slim_lint/filters/auto_indenter.rb +39 -0
- data/lib/slim_lint/filters/control_processor.rb +46 -0
- data/lib/slim_lint/filters/do_inserter.rb +39 -0
- data/lib/slim_lint/filters/end_inserter.rb +74 -0
- data/lib/slim_lint/filters/interpolation.rb +73 -0
- data/lib/slim_lint/filters/multi_flattener.rb +32 -0
- data/lib/slim_lint/filters/splat_processor.rb +20 -0
- data/lib/slim_lint/filters/static_merger.rb +47 -0
- data/lib/slim_lint/lint.rb +70 -0
- data/lib/slim_lint/linter/avoid_multiline_expressions.rb +41 -0
- data/lib/slim_lint/linter/comment_control_statement.rb +26 -0
- data/lib/slim_lint/linter/consecutive_control_statements.rb +26 -0
- data/lib/slim_lint/linter/control_statement_spacing.rb +32 -0
- data/lib/slim_lint/linter/dynamic_output_spacing.rb +77 -0
- data/lib/slim_lint/linter/embedded_engines.rb +18 -0
- data/lib/slim_lint/linter/empty_control_statement.rb +15 -0
- data/lib/slim_lint/linter/empty_lines.rb +24 -0
- data/lib/slim_lint/linter/file_length.rb +18 -0
- data/lib/slim_lint/linter/line_length.rb +18 -0
- data/lib/slim_lint/linter/redundant_div.rb +21 -0
- data/lib/slim_lint/linter/rubocop.rb +131 -0
- data/lib/slim_lint/linter/standard.rb +69 -0
- data/lib/slim_lint/linter/tab.rb +20 -0
- data/lib/slim_lint/linter/tag_case.rb +15 -0
- data/lib/slim_lint/linter/trailing_blank_lines.rb +19 -0
- data/lib/slim_lint/linter/trailing_whitespace.rb +17 -0
- data/lib/slim_lint/linter.rb +93 -0
- data/lib/slim_lint/linter_registry.rb +37 -0
- data/lib/slim_lint/linter_selector.rb +87 -0
- data/lib/slim_lint/logger.rb +103 -0
- data/lib/slim_lint/matcher/anything.rb +11 -0
- data/lib/slim_lint/matcher/base.rb +21 -0
- data/lib/slim_lint/matcher/capture.rb +32 -0
- data/lib/slim_lint/matcher/nothing.rb +13 -0
- data/lib/slim_lint/options.rb +110 -0
- data/lib/slim_lint/parser.rb +584 -0
- data/lib/slim_lint/rake_task.rb +125 -0
- data/lib/slim_lint/report.rb +25 -0
- data/lib/slim_lint/reporter/checkstyle_reporter.rb +42 -0
- data/lib/slim_lint/reporter/default_reporter.rb +40 -0
- data/lib/slim_lint/reporter/emacs_reporter.rb +40 -0
- data/lib/slim_lint/reporter/json_reporter.rb +50 -0
- data/lib/slim_lint/reporter.rb +44 -0
- data/lib/slim_lint/ruby_extract_engine.rb +30 -0
- data/lib/slim_lint/ruby_extractor.rb +175 -0
- data/lib/slim_lint/ruby_parser.rb +32 -0
- data/lib/slim_lint/runner.rb +82 -0
- data/lib/slim_lint/sexp.rb +134 -0
- data/lib/slim_lint/sexp_visitor.rb +150 -0
- data/lib/slim_lint/source_location.rb +45 -0
- data/lib/slim_lint/utils.rb +84 -0
- data/lib/slim_lint/version.rb +6 -0
- data/lib/slim_lint.rb +55 -0
- metadata +218 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6dcf68f7cde5910a4f87dc7f7c1409976006ffde7a554601cb2ff0fad4240dad
|
4
|
+
data.tar.gz: c1b6f4332831e572aa1f3adbb010a254ad752c222531849b2891cd740013a997
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ef7ee3e8871a0eb7218a7994aefe7922411435c75f112ea85998cc9bfb3be4228ebb0bdf6ebc2ba8de308cad2c03975cbbbf2d65b6f1425998ee358e25ba5252
|
7
|
+
data.tar.gz: acaf35418fea782e1e8f1fe1612c0a19668d90c6b3befa00afb638e1998a1b2baead8d32770a94ca2ee0e4c7e0d2077c123912bd684110decff3c58130e545a2
|
data/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
`slim-lint-standard` is released under the MIT license.
|
2
|
+
|
3
|
+
> Copyright (c) 2015 Shane da Silva. http://shane.io
|
4
|
+
>
|
5
|
+
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
> of this software and associated documentation files (the "Software"), to deal
|
7
|
+
> in the Software without restriction, including without limitation the rights
|
8
|
+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
> copies of the Software, and to permit persons to whom the Software is
|
10
|
+
> furnished to do so, subject to the following conditions:
|
11
|
+
>
|
12
|
+
> The above copyright notice and this permission notice shall be included in
|
13
|
+
> all copies or substantial portions of the Software.
|
14
|
+
>
|
15
|
+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
> SOFTWARE.
|
data/config/default.yml
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# Default application configuration that all configurations inherit from.
|
2
|
+
#
|
3
|
+
# This is an opinionated list of which hooks are valuable to run and what their
|
4
|
+
# out of the box settings should be.
|
5
|
+
|
6
|
+
# Whether to ignore frontmatter at the beginning of Slim documents for
|
7
|
+
# frameworks such as Jekyll/Middleman
|
8
|
+
skip_frontmatter: false
|
9
|
+
|
10
|
+
linters:
|
11
|
+
AvoidMultilineExpressions:
|
12
|
+
enabled: true
|
13
|
+
|
14
|
+
CommentControlStatement:
|
15
|
+
enabled: true
|
16
|
+
|
17
|
+
ConsecutiveControlStatements:
|
18
|
+
enabled: true
|
19
|
+
max_consecutive: 2
|
20
|
+
|
21
|
+
ControlStatementSpacing:
|
22
|
+
enabled: true
|
23
|
+
space_after: always
|
24
|
+
|
25
|
+
DynamicOutputSpacing:
|
26
|
+
enabled: true
|
27
|
+
space_before: always
|
28
|
+
space_after: always
|
29
|
+
|
30
|
+
EmbeddedEngines:
|
31
|
+
enabled: false
|
32
|
+
forbidden_engines: []
|
33
|
+
|
34
|
+
EmptyControlStatement:
|
35
|
+
enabled: true
|
36
|
+
|
37
|
+
EmptyLines:
|
38
|
+
enabled: true
|
39
|
+
|
40
|
+
FileLength:
|
41
|
+
enabled: false
|
42
|
+
max: 300
|
43
|
+
|
44
|
+
LineLength:
|
45
|
+
enabled: false
|
46
|
+
max: 80
|
47
|
+
|
48
|
+
RedundantDiv:
|
49
|
+
enabled: true
|
50
|
+
|
51
|
+
RuboCop:
|
52
|
+
enabled: false
|
53
|
+
# These cops are incredibly noisy since the Ruby we extract from Slim
|
54
|
+
# templates isn't well-formatted, so we ignore them.
|
55
|
+
# WARNING: If you define this list in your own .slim-lint.yml file, you'll
|
56
|
+
# be overriding the list defined here.
|
57
|
+
# TODO: Narrow this list to essentials.
|
58
|
+
ignored_cops:
|
59
|
+
- Layout/ArgumentAlignment
|
60
|
+
- Layout/ArrayAlignment
|
61
|
+
- Layout/BlockAlignment
|
62
|
+
- Layout/ClosingParenthesisIndentation
|
63
|
+
- Layout/EmptyLineAfterGuardClause
|
64
|
+
- Layout/EndAlignment
|
65
|
+
- Layout/FirstArgumentIndentation
|
66
|
+
- Layout/FirstArrayElementIndentation
|
67
|
+
- Layout/FirstHashElementIndentation
|
68
|
+
- Layout/FirstParameterIndentation
|
69
|
+
- Layout/HashAlignment
|
70
|
+
- Layout/IndentationConsistency
|
71
|
+
- Layout/IndentationWidth
|
72
|
+
- Layout/InitialIndentation
|
73
|
+
- Layout/LineEndStringConcatenationIndentation
|
74
|
+
- Layout/LineLength
|
75
|
+
- Layout/MultilineArrayBraceLayout
|
76
|
+
- Layout/MultilineAssignmentLayout
|
77
|
+
- Layout/MultilineHashBraceLayout
|
78
|
+
- Layout/MultilineMethodCallBraceLayout
|
79
|
+
- Layout/MultilineMethodCallIndentation
|
80
|
+
- Layout/MultilineMethodDefinitionBraceLayout
|
81
|
+
- Layout/MultilineOperationIndentation
|
82
|
+
- Layout/ParameterAlignment
|
83
|
+
- Layout/TrailingEmptyLines
|
84
|
+
- Layout/TrailingWhitespace
|
85
|
+
- Lint/Void
|
86
|
+
- Metrics/BlockLength
|
87
|
+
- Metrics/BlockNesting
|
88
|
+
- Naming/FileName
|
89
|
+
- Style/FrozenStringLiteralComment
|
90
|
+
- Style/IdenticalConditionalBranches
|
91
|
+
- Style/IfUnlessModifier
|
92
|
+
- Style/Next
|
93
|
+
- Style/WhileUntilDo
|
94
|
+
- Style/WhileUntilModifier
|
95
|
+
|
96
|
+
Standard:
|
97
|
+
enabled: true
|
98
|
+
|
99
|
+
Tab:
|
100
|
+
enabled: true
|
101
|
+
|
102
|
+
TagCase:
|
103
|
+
enabled: true
|
104
|
+
|
105
|
+
TrailingBlankLines:
|
106
|
+
enabled: true
|
107
|
+
|
108
|
+
TrailingWhitespace:
|
109
|
+
enabled: true
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlimLint
|
4
|
+
# Represents an atomic, childless, literal value within an S-expression.
|
5
|
+
#
|
6
|
+
# This creates a light wrapper around literal values of S-expressions so we
|
7
|
+
# can make an {Atom} quack like a {Sexp} without being an {Sexp}.
|
8
|
+
class Atom
|
9
|
+
# Stores the line number of the code in the original document that this Atom
|
10
|
+
# came from.
|
11
|
+
attr_accessor :value, :start, :finish
|
12
|
+
|
13
|
+
# Creates an atom from the specified value.
|
14
|
+
#
|
15
|
+
# @param value [Object]
|
16
|
+
def initialize(value, pos:)
|
17
|
+
@value = value
|
18
|
+
|
19
|
+
@start = pos
|
20
|
+
|
21
|
+
if value.is_a?(String)
|
22
|
+
lines = value.count("\n")
|
23
|
+
chars = 0
|
24
|
+
chars = value.lines.last.size unless value.empty?
|
25
|
+
@finish = [pos[0] + lines, chars + (lines == 0 ? pos[1] : 0)]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def line
|
30
|
+
start[0] if start
|
31
|
+
end
|
32
|
+
|
33
|
+
def location
|
34
|
+
SourceLocation.new(
|
35
|
+
start_line: start[0],
|
36
|
+
start_column: start[1],
|
37
|
+
last_line: (finish || start)[0],
|
38
|
+
last_column: (finish || start)[1]
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns whether this atom is equivalent to another object.
|
43
|
+
#
|
44
|
+
# This defines a helper which unwraps the inner value of the atom to compare
|
45
|
+
# against a literal value, saving us having to do it ourselves everywhere
|
46
|
+
# else.
|
47
|
+
#
|
48
|
+
# @param other [Object]
|
49
|
+
# @return [Boolean]
|
50
|
+
def ==(other)
|
51
|
+
@value == (other.is_a?(Atom) ? other.instance_variable_get(:@value) : other)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns whether this atom matches the given Sexp pattern.
|
55
|
+
#
|
56
|
+
# This exists solely to make an {Atom} quack like a {Sexp}, so we don't have
|
57
|
+
# to manually check the type when doing comparisons elsewhere.
|
58
|
+
#
|
59
|
+
# @param [Array, Object]
|
60
|
+
# @return [Boolean]
|
61
|
+
def match?(pattern)
|
62
|
+
# Delegate matching logic if we're comparing against a matcher
|
63
|
+
if pattern.is_a?(SlimLint::Matcher::Base)
|
64
|
+
return pattern.match?(@value)
|
65
|
+
end
|
66
|
+
|
67
|
+
@value == pattern
|
68
|
+
end
|
69
|
+
|
70
|
+
# Displays the string representation the value this {Atom} wraps.
|
71
|
+
#
|
72
|
+
# @return [String]
|
73
|
+
def to_s
|
74
|
+
@value.to_s
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_array
|
78
|
+
@value
|
79
|
+
end
|
80
|
+
|
81
|
+
# Displays a string representation of this {Atom} suitable for debugging.
|
82
|
+
#
|
83
|
+
# @return [String]
|
84
|
+
def inspect
|
85
|
+
range = +""
|
86
|
+
range << start.join(":") if start
|
87
|
+
range << " => " if start && finish
|
88
|
+
range << finish.join(":") if finish
|
89
|
+
|
90
|
+
"A(#{range}) #{@value.inspect}"
|
91
|
+
end
|
92
|
+
|
93
|
+
# Redirect methods to the value this {Atom} wraps.
|
94
|
+
#
|
95
|
+
# Again, this is for convenience so we don't need to manually unwrap the
|
96
|
+
# value ourselves. It's pretty magical, but results in much DRYer code.
|
97
|
+
#
|
98
|
+
# @param method_sym [Symbol] method that was called
|
99
|
+
# @param args [Array]
|
100
|
+
# @yield block that was passed to the method
|
101
|
+
def method_missing(method_sym, *args, &block)
|
102
|
+
if @value.respond_to?(method_sym)
|
103
|
+
@value.send(method_sym, *args, &block)
|
104
|
+
else
|
105
|
+
super
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# @param method_name [String,Symbol] method name
|
110
|
+
# @param args [Array]
|
111
|
+
def respond_to_missing?(method_name, *args)
|
112
|
+
@value.__send__(:respond_to_missing?, method_name, *args) || super
|
113
|
+
end
|
114
|
+
|
115
|
+
# Return whether this {Atom} or the value it wraps responds to the given
|
116
|
+
# message.
|
117
|
+
#
|
118
|
+
# @param method_sym [Symbol]
|
119
|
+
# @param include_private [Boolean]
|
120
|
+
# @return [Boolean]
|
121
|
+
def respond_to?(method_sym, include_private = false)
|
122
|
+
if super
|
123
|
+
true
|
124
|
+
else
|
125
|
+
@value.respond_to?(method_sym, include_private)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlimLint
|
4
|
+
# Holds the list of captures, providing a convenient interface for accessing
|
5
|
+
# the values and unwrapping them on your behalf.
|
6
|
+
class CaptureMap < Hash
|
7
|
+
# Returns the captured value with the specified name.
|
8
|
+
#
|
9
|
+
# @param capture_name [Symbol]
|
10
|
+
# @return [Object]
|
11
|
+
def [](capture_name)
|
12
|
+
if key?(capture_name)
|
13
|
+
super.value
|
14
|
+
else
|
15
|
+
raise ArgumentError, "Capture #{capture_name.inspect} does not exist!"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "slim_lint"
|
4
|
+
require "slim_lint/options"
|
5
|
+
|
6
|
+
module SlimLint
|
7
|
+
# Command line application interface.
|
8
|
+
class CLI
|
9
|
+
# Exit codes
|
10
|
+
# @see https://man.openbsd.org/sysexits.3
|
11
|
+
EX_OK = 0
|
12
|
+
EX_USAGE = 64
|
13
|
+
EX_DATAERR = 65
|
14
|
+
EX_NOINPUT = 67
|
15
|
+
EX_SOFTWARE = 70
|
16
|
+
EX_CONFIG = 78
|
17
|
+
|
18
|
+
# Create a CLI that outputs to the specified logger.
|
19
|
+
#
|
20
|
+
# @param logger [SlimLint::Logger]
|
21
|
+
def initialize(logger)
|
22
|
+
@log = logger
|
23
|
+
end
|
24
|
+
|
25
|
+
# Parses the given command-line arguments and executes appropriate logic
|
26
|
+
# based on those arguments.
|
27
|
+
#
|
28
|
+
# @param args [Array<String>] command line arguments
|
29
|
+
# @return [Integer] exit status code
|
30
|
+
def run(args)
|
31
|
+
options = SlimLint::Options.new.parse(args)
|
32
|
+
act_on_options(options)
|
33
|
+
rescue => e
|
34
|
+
handle_exception(e)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :log
|
40
|
+
|
41
|
+
# Given the provided options, execute the appropriate command.
|
42
|
+
#
|
43
|
+
# @return [Integer] exit status code
|
44
|
+
def act_on_options(options)
|
45
|
+
log.color_enabled = options.fetch(:color, log.tty?)
|
46
|
+
|
47
|
+
if options[:help]
|
48
|
+
print_help(options)
|
49
|
+
EX_OK
|
50
|
+
elsif options[:version] || options[:verbose_version]
|
51
|
+
print_version(options)
|
52
|
+
EX_OK
|
53
|
+
elsif options[:show_linters]
|
54
|
+
print_available_linters
|
55
|
+
EX_OK
|
56
|
+
elsif options[:show_reporters]
|
57
|
+
print_available_reporters
|
58
|
+
EX_OK
|
59
|
+
else
|
60
|
+
scan_for_lints(options)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Outputs a message and returns an appropriate error code for the specified
|
65
|
+
# exception.
|
66
|
+
def handle_exception(exception)
|
67
|
+
case exception
|
68
|
+
when SlimLint::Exceptions::ConfigurationError
|
69
|
+
log.error exception.message
|
70
|
+
EX_CONFIG
|
71
|
+
when SlimLint::Exceptions::InvalidCLIOption
|
72
|
+
log.error exception.message
|
73
|
+
log.log "Run `#{APP_NAME}` --help for usage documentation"
|
74
|
+
EX_USAGE
|
75
|
+
when SlimLint::Exceptions::InvalidFilePath
|
76
|
+
log.error exception.message
|
77
|
+
EX_NOINPUT
|
78
|
+
when SlimLint::Exceptions::NoLintersError
|
79
|
+
log.error exception.message
|
80
|
+
EX_NOINPUT
|
81
|
+
else
|
82
|
+
print_unexpected_exception(exception)
|
83
|
+
EX_SOFTWARE
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Scans the files specified by the given options for lints.
|
88
|
+
#
|
89
|
+
# @return [Integer] exit status code
|
90
|
+
def scan_for_lints(options)
|
91
|
+
report = Runner.new.run(options)
|
92
|
+
print_report(report, options)
|
93
|
+
report.failed? ? EX_DATAERR : EX_OK
|
94
|
+
end
|
95
|
+
|
96
|
+
# Outputs a report of the linter run using the specified reporter.
|
97
|
+
def print_report(report, options)
|
98
|
+
reporter = options.fetch(:reporter,
|
99
|
+
SlimLint::Reporter::DefaultReporter).new(log)
|
100
|
+
reporter.display_report(report)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Outputs a list of all currently available linters.
|
104
|
+
def print_available_linters
|
105
|
+
log.info "Available linters:"
|
106
|
+
|
107
|
+
linter_names = SlimLint::LinterRegistry.linters.map do |linter|
|
108
|
+
linter.name.split("::").last
|
109
|
+
end
|
110
|
+
|
111
|
+
linter_names.sort.each do |linter_name|
|
112
|
+
log.log " - #{linter_name}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Outputs a list of currently available reporters.
|
117
|
+
def print_available_reporters
|
118
|
+
log.info "Available reporters:"
|
119
|
+
|
120
|
+
reporter_names = SlimLint::Reporter.descendants.map do |reporter|
|
121
|
+
reporter.name.split("::").last.sub(/Reporter$/, "").downcase
|
122
|
+
end
|
123
|
+
|
124
|
+
reporter_names.sort.each do |reporter_name|
|
125
|
+
log.log " - #{reporter_name}"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Outputs help documentation.
|
130
|
+
def print_help(options)
|
131
|
+
log.log options[:help]
|
132
|
+
end
|
133
|
+
|
134
|
+
# Outputs the application name and version.
|
135
|
+
def print_version(options)
|
136
|
+
log.log "#{SlimLint::APP_NAME} #{SlimLint::VERSION}"
|
137
|
+
|
138
|
+
if options[:verbose_version]
|
139
|
+
log.log "slim #{Gem.loaded_specs["slim"].version}"
|
140
|
+
log.log "rubocop #{Gem.loaded_specs["rubocop"].version}"
|
141
|
+
log.log RUBY_DESCRIPTION
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Outputs the backtrace of an exception with instructions on how to report
|
146
|
+
# the issue.
|
147
|
+
def print_unexpected_exception(exception)
|
148
|
+
log.bold_error exception.message
|
149
|
+
log.error exception.backtrace.join("\n")
|
150
|
+
log.warning "Report this bug at ", false
|
151
|
+
log.info SlimLint::BUG_REPORT_URL
|
152
|
+
log.newline
|
153
|
+
log.success "To help fix this issue, please include:"
|
154
|
+
log.log "- The above stack trace"
|
155
|
+
log.log "- slim-lint-standard version: ", false
|
156
|
+
log.info SlimLint::VERSION
|
157
|
+
log.log "- RuboCop version: ", false
|
158
|
+
log.info Gem.loaded_specs["rubocop"].version
|
159
|
+
if Gem.loaded_specs["standard"]
|
160
|
+
log.log "- Standard version: ", false
|
161
|
+
log.info Gem.loaded_specs["standard"].version
|
162
|
+
end
|
163
|
+
log.log "- Ruby version: ", false
|
164
|
+
log.info RUBY_VERSION
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlimLint
|
4
|
+
# Stores runtime configuration for the application.
|
5
|
+
#
|
6
|
+
# The purpose of this class is to validate and ensure all configurations
|
7
|
+
# satisfy some basic pre-conditions so other parts of the application don't
|
8
|
+
# have to check the configuration for errors. It should have no knowledge of
|
9
|
+
# how these configuration values are ultimately used.
|
10
|
+
class Configuration
|
11
|
+
# Internal hash storing the configuration.
|
12
|
+
attr_reader :hash
|
13
|
+
|
14
|
+
# Creates a configuration from the given options hash.
|
15
|
+
#
|
16
|
+
# @param options [Hash]
|
17
|
+
def initialize(options)
|
18
|
+
@hash = options
|
19
|
+
validate
|
20
|
+
end
|
21
|
+
|
22
|
+
# Access the configuration as if it were a hash.
|
23
|
+
#
|
24
|
+
# @param key [String]
|
25
|
+
# @return [Array,Hash,Number,String]
|
26
|
+
def [](key)
|
27
|
+
@hash[key]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Compares this configuration with another.
|
31
|
+
#
|
32
|
+
# @param other [SlimLint::Configuration]
|
33
|
+
# @return [true,false] whether the given configuration is equivalent
|
34
|
+
def ==(other)
|
35
|
+
super || @hash == other.hash
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns a non-modifiable configuration for the specified linter.
|
39
|
+
#
|
40
|
+
# @param linter [SlimLint::Linter,Class]
|
41
|
+
def for_linter(linter)
|
42
|
+
linter_name =
|
43
|
+
case linter
|
44
|
+
when SlimLint::Linter
|
45
|
+
linter.name
|
46
|
+
when Class
|
47
|
+
linter.name.split("::").last
|
48
|
+
when String
|
49
|
+
linter.split("::").last
|
50
|
+
end
|
51
|
+
|
52
|
+
@hash["linters"].fetch(linter_name, {}).dup.freeze
|
53
|
+
end
|
54
|
+
|
55
|
+
# Merges the given configuration with this one, returning a new
|
56
|
+
# {Configuration}. The provided configuration will either add to or replace
|
57
|
+
# any options defined in this configuration.
|
58
|
+
#
|
59
|
+
# @param config [SlimLint::Configuration]
|
60
|
+
def merge(config)
|
61
|
+
self.class.new(smart_merge(@hash, config.hash))
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# Merge two hashes such that nested hashes are merged rather than replaced.
|
67
|
+
#
|
68
|
+
# @param parent [Hash]
|
69
|
+
# @param child [Hash]
|
70
|
+
# @return [Hash]
|
71
|
+
def smart_merge(parent, child)
|
72
|
+
parent.merge(child) do |_key, old, new|
|
73
|
+
case old
|
74
|
+
when Hash
|
75
|
+
smart_merge(old, new)
|
76
|
+
else
|
77
|
+
new
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Validates the configuration for any invalid options, normalizing it where
|
83
|
+
# possible.
|
84
|
+
def validate
|
85
|
+
ensure_exclude_option_array_exists
|
86
|
+
ensure_linter_section_exists
|
87
|
+
ensure_linter_include_exclude_arrays_exist
|
88
|
+
end
|
89
|
+
|
90
|
+
# Ensures the `exclude` global option is an array.
|
91
|
+
def ensure_exclude_option_array_exists
|
92
|
+
@hash["exclude"] = Array(@hash["exclude"])
|
93
|
+
end
|
94
|
+
|
95
|
+
# Ensures the `linters` configuration section exists.
|
96
|
+
def ensure_linter_section_exists
|
97
|
+
@hash["linters"] ||= {}
|
98
|
+
end
|
99
|
+
|
100
|
+
# Ensure `include` and `exclude` options for linters are arrays
|
101
|
+
# (since users can specify a single string glob pattern for convenience)
|
102
|
+
def ensure_linter_include_exclude_arrays_exist
|
103
|
+
@hash["linters"].each_key do |linter_name|
|
104
|
+
%w[include exclude].each do |option|
|
105
|
+
linter_config = @hash["linters"][linter_name]
|
106
|
+
linter_config[option] = Array(linter_config[option])
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require "yaml"
|
5
|
+
|
6
|
+
module SlimLint
|
7
|
+
# Manages configuration file loading.
|
8
|
+
class ConfigurationLoader
|
9
|
+
DEFAULT_CONFIG_PATH = File.join(SlimLint::HOME, "config", "default.yml").freeze
|
10
|
+
CONFIG_FILE_NAME = ".slim-lint.yml"
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# Load configuration file given the current working directory the
|
14
|
+
# application is running within.
|
15
|
+
def load_applicable_config
|
16
|
+
directory = File.expand_path(Dir.pwd)
|
17
|
+
config_file = possible_config_files(directory).find(&:file?)
|
18
|
+
|
19
|
+
if config_file
|
20
|
+
load_file(config_file.to_path)
|
21
|
+
else
|
22
|
+
default_configuration
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Loads the built-in default configuration.
|
27
|
+
def default_configuration
|
28
|
+
@default_configuration ||= load_from_file(DEFAULT_CONFIG_PATH)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Loads a configuration, ensuring it extends the default configuration.
|
32
|
+
#
|
33
|
+
# @param file [String]
|
34
|
+
# @return [SlimLint::Configuration]
|
35
|
+
def load_file(file)
|
36
|
+
config = load_from_file(file)
|
37
|
+
|
38
|
+
default_configuration.merge(config)
|
39
|
+
rescue Psych::SyntaxError, Errno::ENOENT => e
|
40
|
+
raise SlimLint::Exceptions::ConfigurationError,
|
41
|
+
"Unable to load configuration from '#{file}': #{e}",
|
42
|
+
e.backtrace
|
43
|
+
end
|
44
|
+
|
45
|
+
# Creates a configuration from the specified hash, ensuring it extends the
|
46
|
+
# default configuration.
|
47
|
+
#
|
48
|
+
# @param hash [Hash]
|
49
|
+
# @return [SlimLint::Configuration]
|
50
|
+
def load_hash(hash)
|
51
|
+
config = SlimLint::Configuration.new(hash)
|
52
|
+
|
53
|
+
default_configuration.merge(config)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Parses and loads a configuration from the given file.
|
59
|
+
#
|
60
|
+
# @param file [String]
|
61
|
+
# @return [SlimLint::Configuration]
|
62
|
+
def load_from_file(file)
|
63
|
+
hash =
|
64
|
+
if (yaml = YAML.load_file(file))
|
65
|
+
yaml.to_hash
|
66
|
+
else
|
67
|
+
{}
|
68
|
+
end
|
69
|
+
|
70
|
+
SlimLint::Configuration.new(hash)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns a list of possible configuration files given the context of the
|
74
|
+
# specified directory.
|
75
|
+
#
|
76
|
+
# @param directory [String]
|
77
|
+
# @return [Array<Pathname>]
|
78
|
+
def possible_config_files(directory)
|
79
|
+
files = Pathname.new(directory)
|
80
|
+
.enum_for(:ascend)
|
81
|
+
.map { |path| path + CONFIG_FILE_NAME }
|
82
|
+
files << Pathname.new(CONFIG_FILE_NAME)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Global application constants.
|
4
|
+
module SlimLint
|
5
|
+
HOME = File.expand_path(File.join(File.dirname(__FILE__), "..", "..")).freeze
|
6
|
+
APP_NAME = "slim-lint-standard"
|
7
|
+
|
8
|
+
REPO_URL = "https://github.com/pvande/slim-lint-standard"
|
9
|
+
BUG_REPORT_URL = "#{REPO_URL}/issues"
|
10
|
+
end
|