slim_lint_standard 0.0.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/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
|