slim_lint 0.2.0 → 0.3.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 +4 -4
- data/bin/slim-lint +0 -1
- data/lib/slim_lint/atom.rb +91 -0
- data/lib/slim_lint/capture_map.rb +17 -0
- data/lib/slim_lint/cli.rb +25 -8
- data/lib/slim_lint/configuration.rb +38 -31
- data/lib/slim_lint/configuration_loader.rb +21 -5
- data/lib/slim_lint/document.rb +20 -4
- data/lib/slim_lint/engine.rb +6 -0
- data/lib/slim_lint/file_finder.rb +13 -4
- data/lib/slim_lint/filters/inject_line_numbers.rb +7 -2
- data/lib/slim_lint/filters/sexp_converter.rb +4 -0
- data/lib/slim_lint/lint.rb +17 -1
- data/lib/slim_lint/linter/comment_control_statement.rb +2 -2
- data/lib/slim_lint/linter/consecutive_control_statements.rb +1 -16
- data/lib/slim_lint/linter/empty_control_statement.rb +1 -1
- data/lib/slim_lint/linter/redundant_div.rb +8 -5
- data/lib/slim_lint/linter/rubocop.rb +40 -18
- data/lib/slim_lint/linter/tag_case.rb +1 -1
- data/lib/slim_lint/linter.rb +19 -6
- data/lib/slim_lint/linter_registry.rb +13 -2
- data/lib/slim_lint/linter_selector.rb +74 -0
- data/lib/slim_lint/logger.rb +5 -9
- data/lib/slim_lint/matcher/anything.rb +9 -0
- data/lib/slim_lint/matcher/base.rb +19 -0
- data/lib/slim_lint/matcher/capture.rb +30 -0
- data/lib/slim_lint/matcher/nothing.rb +11 -0
- data/lib/slim_lint/options.rb +16 -6
- data/lib/slim_lint/rake_task.rb +15 -0
- data/lib/slim_lint/report.rb +7 -0
- data/lib/slim_lint/reporter/default_reporter.rb +2 -2
- data/lib/slim_lint/reporter/json_reporter.rb +15 -10
- data/lib/slim_lint/reporter.rb +17 -11
- data/lib/slim_lint/ruby_extractor.rb +2 -0
- data/lib/slim_lint/runner.rb +43 -39
- data/lib/slim_lint/sexp.rb +43 -30
- data/lib/slim_lint/sexp_visitor.rb +66 -28
- data/lib/slim_lint/utils.rb +17 -0
- data/lib/slim_lint/version.rb +1 -1
- data/lib/slim_lint.rb +10 -1
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 66e3790d144eb2141cb93b53835a447cfe9e1db8
|
4
|
+
data.tar.gz: 7b07131e272e1b913926061eb37703d7e0738f01
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d776fde028a429d5a44ae2cdc4092705e759a96f82dca6c2b59ad6a3fc1280823e86ecee38fa642f3720d048567ab31a3dd1bd5bf72a48ef3737c2b1d300843f
|
7
|
+
data.tar.gz: e617bc89440a83feefec3beabaa186ef57fb8c5433fd7b134610deaf631a4b4ad3c0e1e70474255145d2b40fc5d19ee3f19191ab102329b4f5a5dc27d9995717
|
data/bin/slim-lint
CHANGED
@@ -0,0 +1,91 @@
|
|
1
|
+
module SlimLint
|
2
|
+
# Represents an atomic, childless, literal value within an S-expression.
|
3
|
+
#
|
4
|
+
# This creates a light wrapper around literal values of S-expressions so we
|
5
|
+
# can make an {Atom} quack like a {Sexp} without being an {Sexp}.
|
6
|
+
class Atom
|
7
|
+
# Creates an atom from the specified value.
|
8
|
+
#
|
9
|
+
# @param value [Object]
|
10
|
+
def initialize(value)
|
11
|
+
@value = value
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns whether this atom is equivalent to another object.
|
15
|
+
#
|
16
|
+
# This defines a helper which unwraps the inner value of the atom to compare
|
17
|
+
# against a literal value, saving us having to do it ourselves everywhere
|
18
|
+
# else.
|
19
|
+
#
|
20
|
+
# @param other [Object]
|
21
|
+
# @return [Boolean]
|
22
|
+
def ==(other)
|
23
|
+
case other
|
24
|
+
when Atom
|
25
|
+
@value == other.instance_variable_get(:@value)
|
26
|
+
else
|
27
|
+
@value == other
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns whether this atom matches the given Sexp pattern.
|
32
|
+
#
|
33
|
+
# This exists solely to make an {Atom} quack like a {Sexp}, so we don't have
|
34
|
+
# to manually check the type when doing comparisons elsewhere.
|
35
|
+
#
|
36
|
+
# @param [Array, Object]
|
37
|
+
# @return [Boolean]
|
38
|
+
def match?(pattern)
|
39
|
+
# Delegate matching logic if we're comparing against a matcher
|
40
|
+
if pattern.is_a?(SlimLint::Matcher::Base)
|
41
|
+
return pattern.match?(@value)
|
42
|
+
end
|
43
|
+
|
44
|
+
@value == pattern
|
45
|
+
end
|
46
|
+
|
47
|
+
# Displays the string representation the value this {Atom} wraps.
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
def to_s
|
51
|
+
@value.to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
# Displays a string representation of this {Atom} suitable for debugging.
|
55
|
+
#
|
56
|
+
# @return [String]
|
57
|
+
def inspect
|
58
|
+
"<#Atom #{@value.inspect}>"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Redirect methods to the value this {Atom} wraps.
|
62
|
+
#
|
63
|
+
# Again, this is for convenience so we don't need to manually unwrap the
|
64
|
+
# value ourselves. It's pretty magical, but results in much DRYer code.
|
65
|
+
#
|
66
|
+
# @param method_sym [Symbol] method that was called
|
67
|
+
# @param args [Array]
|
68
|
+
# @yield block that was passed to the method
|
69
|
+
def method_missing(method_sym, *args, &block)
|
70
|
+
if @value.respond_to?(method_sym)
|
71
|
+
@value.send(method_sym, *args, &block)
|
72
|
+
else
|
73
|
+
super
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Return whether this {Atom} or the value it wraps responds to the given
|
78
|
+
# message.
|
79
|
+
#
|
80
|
+
# @param method_sym [Symbol]
|
81
|
+
# @param include_private [Boolean]
|
82
|
+
# @return [Boolean]
|
83
|
+
def respond_to?(method_sym, include_private = false)
|
84
|
+
if super
|
85
|
+
true
|
86
|
+
else
|
87
|
+
@value.respond_to?(method_sym, include_private)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SlimLint
|
2
|
+
# Holds the list of captures, providing a convenient interface for accessing
|
3
|
+
# the values and unwrapping them on your behalf.
|
4
|
+
class CaptureMap < Hash
|
5
|
+
# Returns the captured value with the specified name.
|
6
|
+
#
|
7
|
+
# @param capture_name [Symbol]
|
8
|
+
# @return [Object]
|
9
|
+
def [](capture_name)
|
10
|
+
if key?(capture_name)
|
11
|
+
super.value
|
12
|
+
else
|
13
|
+
raise ArgumentError, "Capture #{capture_name.inspect} does not exist!"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/slim_lint/cli.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'slim_lint'
|
1
2
|
require 'slim_lint/options'
|
2
3
|
|
3
4
|
require 'sysexits'
|
@@ -5,8 +6,8 @@ require 'sysexits'
|
|
5
6
|
module SlimLint
|
6
7
|
# Command line application interface.
|
7
8
|
class CLI
|
8
|
-
|
9
|
-
|
9
|
+
# Create a CLI that outputs to the specified logger.
|
10
|
+
#
|
10
11
|
# @param logger [SlimLint::Logger]
|
11
12
|
def initialize(logger)
|
12
13
|
@log = logger
|
@@ -16,7 +17,7 @@ module SlimLint
|
|
16
17
|
# based on those arguments.
|
17
18
|
#
|
18
19
|
# @param args [Array<String>] command line arguments
|
19
|
-
# @return [
|
20
|
+
# @return [Integer] exit status code
|
20
21
|
def run(args)
|
21
22
|
options = SlimLint::Options.new.parse(args)
|
22
23
|
act_on_options(options)
|
@@ -28,6 +29,9 @@ module SlimLint
|
|
28
29
|
|
29
30
|
attr_reader :log
|
30
31
|
|
32
|
+
# Given the provided options, execute the appropriate command.
|
33
|
+
#
|
34
|
+
# @return [Integer] exit status code
|
31
35
|
def act_on_options(options)
|
32
36
|
log.color_enabled = options.fetch(:color, log.tty?)
|
33
37
|
|
@@ -48,6 +52,8 @@ module SlimLint
|
|
48
52
|
end
|
49
53
|
end
|
50
54
|
|
55
|
+
# Outputs a message and returns an appropriate error code for the specified
|
56
|
+
# exception.
|
51
57
|
def handle_exception(ex)
|
52
58
|
case ex
|
53
59
|
when SlimLint::Exceptions::ConfigurationError
|
@@ -69,21 +75,27 @@ module SlimLint
|
|
69
75
|
end
|
70
76
|
end
|
71
77
|
|
78
|
+
# Scans the files specified by the given options for lints.
|
79
|
+
#
|
80
|
+
# @return [Integer] exit status code
|
72
81
|
def scan_for_lints(options)
|
73
82
|
report = Runner.new.run(options)
|
74
83
|
print_report(report, options)
|
75
84
|
report.failed? ? Sysexits::EX_DATAERR : Sysexits::EX_OK
|
76
85
|
end
|
77
86
|
|
87
|
+
# Outputs a report of the linter run using the specified reporter.
|
78
88
|
def print_report(report, options)
|
79
|
-
reporter = options.fetch(:reporter,
|
80
|
-
|
89
|
+
reporter = options.fetch(:reporter,
|
90
|
+
SlimLint::Reporter::DefaultReporter).new(log)
|
91
|
+
reporter.display_report(report)
|
81
92
|
end
|
82
93
|
|
94
|
+
# Outputs a list of all currently available linters.
|
83
95
|
def print_available_linters
|
84
96
|
log.info 'Available linters:'
|
85
97
|
|
86
|
-
linter_names = LinterRegistry.linters.map do |linter|
|
98
|
+
linter_names = SlimLint::LinterRegistry.linters.map do |linter|
|
87
99
|
linter.name.split('::').last
|
88
100
|
end
|
89
101
|
|
@@ -92,10 +104,11 @@ module SlimLint
|
|
92
104
|
end
|
93
105
|
end
|
94
106
|
|
107
|
+
# Outputs a list of currently available reporters.
|
95
108
|
def print_available_reporters
|
96
109
|
log.info 'Available reporters:'
|
97
110
|
|
98
|
-
reporter_names = Reporter.descendants.map do |reporter|
|
111
|
+
reporter_names = SlimLint::Reporter.descendants.map do |reporter|
|
99
112
|
reporter.name.split('::').last.sub(/Reporter$/, '').downcase
|
100
113
|
end
|
101
114
|
|
@@ -104,14 +117,18 @@ module SlimLint
|
|
104
117
|
end
|
105
118
|
end
|
106
119
|
|
120
|
+
# Outputs help documentation.
|
107
121
|
def print_help(options)
|
108
122
|
log.log options[:help]
|
109
123
|
end
|
110
124
|
|
125
|
+
# Outputs the application name and version.
|
111
126
|
def print_version
|
112
|
-
log.log "#{APP_NAME} #{SlimLint::VERSION}"
|
127
|
+
log.log "#{SlimLint::APP_NAME} #{SlimLint::VERSION}"
|
113
128
|
end
|
114
129
|
|
130
|
+
# Outputs the backtrace of an exception with instructions on how to report
|
131
|
+
# the issue.
|
115
132
|
def print_unexpected_exception(ex)
|
116
133
|
log.bold_error ex.message
|
117
134
|
log.error ex.backtrace.join("\n")
|
@@ -1,6 +1,12 @@
|
|
1
1
|
module SlimLint
|
2
2
|
# Stores runtime configuration for the application.
|
3
|
+
#
|
4
|
+
# The purpose of this class is to validate and ensure all configurations
|
5
|
+
# satisfy some basic pre-conditions so other parts of the application don't
|
6
|
+
# have to check the configuration for errors. It should have no knowledge of
|
7
|
+
# how these configuration values are ultimately used.
|
3
8
|
class Configuration
|
9
|
+
# Internal hash storing the configuration.
|
4
10
|
attr_reader :hash
|
5
11
|
|
6
12
|
# Creates a configuration from the given options hash.
|
@@ -11,6 +17,8 @@ module SlimLint
|
|
11
17
|
validate
|
12
18
|
end
|
13
19
|
|
20
|
+
# Access the configuration as if it were a hash.
|
21
|
+
#
|
14
22
|
# @param key [String]
|
15
23
|
# @return [Array,Hash,Number,String]
|
16
24
|
def [](key)
|
@@ -36,19 +44,9 @@ module SlimLint
|
|
36
44
|
linter.name.split('::').last
|
37
45
|
when SlimLint::Linter
|
38
46
|
linter.name
|
39
|
-
else
|
40
|
-
linter.to_s
|
41
47
|
end
|
42
48
|
|
43
|
-
|
44
|
-
@hash['linters'].fetch(linter_name, {})).freeze
|
45
|
-
end
|
46
|
-
|
47
|
-
# Returns whether the specified linter is enabled by this configuration.
|
48
|
-
#
|
49
|
-
# @param linter [SlimLint::Linter,String]
|
50
|
-
def linter_enabled?(linter)
|
51
|
-
for_linter(linter)['enabled'] != false
|
49
|
+
@hash['linters'].fetch(linter_name, {}).dup.freeze
|
52
50
|
end
|
53
51
|
|
54
52
|
# Merges the given configuration with this one, returning a new
|
@@ -62,13 +60,11 @@ module SlimLint
|
|
62
60
|
|
63
61
|
private
|
64
62
|
|
65
|
-
#
|
66
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
71
|
-
|
63
|
+
# Merge two hashes such that nested hashes are merged rather than replaced.
|
64
|
+
#
|
65
|
+
# @param parent [Hash]
|
66
|
+
# @param child [Hash]
|
67
|
+
# @return [Hash]
|
72
68
|
def smart_merge(parent, child)
|
73
69
|
parent.merge(child) do |_key, old, new|
|
74
70
|
case old
|
@@ -80,21 +76,32 @@ module SlimLint
|
|
80
76
|
end
|
81
77
|
end
|
82
78
|
|
83
|
-
|
84
|
-
|
85
|
-
|
79
|
+
# Validates the configuration for any invalid options, normalizing it where
|
80
|
+
# possible.
|
81
|
+
def validate
|
82
|
+
ensure_exclude_option_array_exists
|
83
|
+
ensure_linter_section_exists
|
84
|
+
ensure_linter_include_exclude_arrays_exist
|
86
85
|
end
|
87
86
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
87
|
+
# Ensures the `exclude` global option is an array.
|
88
|
+
def ensure_exclude_option_array_exists
|
89
|
+
@hash['exclude'] = Array(@hash['exclude'])
|
90
|
+
end
|
91
|
+
|
92
|
+
# Ensures the `linters` configuration section exists.
|
93
|
+
def ensure_linter_section_exists
|
94
|
+
@hash['linters'] ||= {}
|
95
|
+
end
|
96
|
+
|
97
|
+
# Ensure `include` and `exclude` options for linters are arrays
|
98
|
+
# (since users can specify a single string glob pattern for convenience)
|
99
|
+
def ensure_linter_include_exclude_arrays_exist
|
100
|
+
@hash['linters'].keys.each do |linter_name|
|
101
|
+
%w[include exclude].each do |option|
|
102
|
+
linter_config = @hash['linters'][linter_name]
|
103
|
+
linter_config[option] = Array(linter_config[option])
|
104
|
+
end
|
98
105
|
end
|
99
106
|
end
|
100
107
|
end
|
@@ -8,6 +8,8 @@ module SlimLint
|
|
8
8
|
CONFIG_FILE_NAME = '.slim-lint.yml'
|
9
9
|
|
10
10
|
class << self
|
11
|
+
# Load configuration file given the current working directory the
|
12
|
+
# application is running within.
|
11
13
|
def load_applicable_config
|
12
14
|
directory = File.expand_path(Dir.pwd)
|
13
15
|
config_file = possible_config_files(directory).find(&:file?)
|
@@ -19,33 +21,42 @@ module SlimLint
|
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
24
|
+
# Loads the built-in default configuration.
|
22
25
|
def default_configuration
|
23
26
|
@default_config ||= load_from_file(DEFAULT_CONFIG_PATH)
|
24
27
|
end
|
25
28
|
|
26
29
|
# Loads a configuration, ensuring it extends the default configuration.
|
30
|
+
#
|
31
|
+
# @param file [String]
|
32
|
+
# @return [SlimLint::Configuration]
|
27
33
|
def load_file(file)
|
28
34
|
config = load_from_file(file)
|
29
35
|
|
30
36
|
default_configuration.merge(config)
|
31
|
-
rescue => error
|
37
|
+
rescue Psych::SyntaxError, Errno::ENOENT => error
|
32
38
|
raise SlimLint::Exceptions::ConfigurationError,
|
33
39
|
"Unable to load configuration from '#{file}': #{error}",
|
34
40
|
error.backtrace
|
35
41
|
end
|
36
42
|
|
43
|
+
# Creates a configuration from the specified hash, ensuring it extends the
|
44
|
+
# default configuration.
|
45
|
+
#
|
46
|
+
# @param hash [Hash]
|
47
|
+
# @return [SlimLint::Configuration]
|
37
48
|
def load_hash(hash)
|
38
49
|
config = SlimLint::Configuration.new(hash)
|
39
50
|
|
40
51
|
default_configuration.merge(config)
|
41
|
-
rescue => error
|
42
|
-
raise SlimLint::Exceptions::ConfigurationError,
|
43
|
-
"Unable to load configuration from '#{file}': #{error}",
|
44
|
-
error.backtrace
|
45
52
|
end
|
46
53
|
|
47
54
|
private
|
48
55
|
|
56
|
+
# Parses and loads a configuration from the given file.
|
57
|
+
#
|
58
|
+
# @param file [String]
|
59
|
+
# @return [SlimLint::Configuration]
|
49
60
|
def load_from_file(file)
|
50
61
|
hash =
|
51
62
|
if yaml = YAML.load_file(file)
|
@@ -57,6 +68,11 @@ module SlimLint
|
|
57
68
|
SlimLint::Configuration.new(hash)
|
58
69
|
end
|
59
70
|
|
71
|
+
# Returns a list of possible configuration files given the context of the
|
72
|
+
# specified directory.
|
73
|
+
#
|
74
|
+
# @param directory [String]
|
75
|
+
# @return [Array<Pathname>]
|
60
76
|
def possible_config_files(directory)
|
61
77
|
files = Pathname.new(directory)
|
62
78
|
.enum_for(:ascend)
|
data/lib/slim_lint/document.rb
CHANGED
@@ -1,7 +1,23 @@
|
|
1
1
|
module SlimLint
|
2
2
|
# Represents a parsed Slim document and its associated metadata.
|
3
3
|
class Document
|
4
|
-
|
4
|
+
# File name given to source code parsed from just a string.
|
5
|
+
STRING_SOURCE = '(string)'
|
6
|
+
|
7
|
+
# @return [SlimLint::Configuration] Configuration used to parse template
|
8
|
+
attr_reader :config
|
9
|
+
|
10
|
+
# @return [String] Slim template file path
|
11
|
+
attr_reader :file
|
12
|
+
|
13
|
+
# @return [SlimLint::Sexp] Sexpression representing the parsed document
|
14
|
+
attr_reader :sexp
|
15
|
+
|
16
|
+
# @return [String] original source code
|
17
|
+
attr_reader :source
|
18
|
+
|
19
|
+
# @return [Array<String>] original source code as an array of lines
|
20
|
+
attr_reader :source_lines
|
5
21
|
|
6
22
|
# Parses the specified Slim code into a {Document}.
|
7
23
|
#
|
@@ -11,7 +27,7 @@ module SlimLint
|
|
11
27
|
# @raise [Slim::Parser::Error] if there was a problem parsing the document
|
12
28
|
def initialize(source, options)
|
13
29
|
@config = options[:config]
|
14
|
-
@file = options.fetch(:file,
|
30
|
+
@file = options.fetch(:file, STRING_SOURCE)
|
15
31
|
|
16
32
|
process_source(source)
|
17
33
|
end
|
@@ -24,8 +40,8 @@ module SlimLint
|
|
24
40
|
@source = strip_frontmatter(source)
|
25
41
|
@source_lines = @source.split("\n")
|
26
42
|
|
27
|
-
|
28
|
-
@sexp =
|
43
|
+
engine = SlimLint::Engine.new(file: @file)
|
44
|
+
@sexp = engine.parse(source)
|
29
45
|
end
|
30
46
|
|
31
47
|
# Removes YAML frontmatter
|
data/lib/slim_lint/engine.rb
CHANGED
@@ -23,5 +23,11 @@ module SlimLint
|
|
23
23
|
|
24
24
|
# Annotates Sexps with line numbers
|
25
25
|
use SlimLint::Filters::InjectLineNumbers
|
26
|
+
|
27
|
+
# Parses the given source code into a Sexp.
|
28
|
+
#
|
29
|
+
# @param source [String] source code to parse
|
30
|
+
# @return [SlimLint::Sexp] parsed Sexp
|
31
|
+
alias_method :parse, :call
|
26
32
|
end
|
27
33
|
end
|
@@ -8,6 +8,8 @@ module SlimLint
|
|
8
8
|
# is specified instead of a file.
|
9
9
|
VALID_EXTENSIONS = %w[.slim]
|
10
10
|
|
11
|
+
# Create a file finder using the specified configuration.
|
12
|
+
#
|
11
13
|
# @param config [SlimLint::Configuration]
|
12
14
|
def initialize(config)
|
13
15
|
@config = config
|
@@ -22,15 +24,18 @@ module SlimLint
|
|
22
24
|
def find(patterns, excluded_patterns)
|
23
25
|
extract_files_from(patterns).reject do |file|
|
24
26
|
excluded_patterns.any? do |exclusion_glob|
|
25
|
-
::
|
26
|
-
::File::FNM_PATHNAME | # Wildcards don't match path separators
|
27
|
-
::File::FNM_DOTMATCH) # `*` wildcard matches dotfiles
|
27
|
+
SlimLint::Utils.any_glob_matches?(exclusion_glob, file)
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
32
|
private
|
33
33
|
|
34
|
+
# Extract the list of matching files given the list of glob patterns, file
|
35
|
+
# paths, and directories.
|
36
|
+
#
|
37
|
+
# @param patterns [Array<String>]
|
38
|
+
# @return [Array<String>]
|
34
39
|
def extract_files_from(patterns) # rubocop:disable MethodLength
|
35
40
|
files = []
|
36
41
|
|
@@ -57,9 +62,13 @@ module SlimLint
|
|
57
62
|
end
|
58
63
|
end
|
59
64
|
|
60
|
-
files.uniq
|
65
|
+
files.uniq.sort
|
61
66
|
end
|
62
67
|
|
68
|
+
# Whether the given file should be treated as a Slim file.
|
69
|
+
#
|
70
|
+
# @param file [String]
|
71
|
+
# @return [Boolean]
|
63
72
|
def slim_file?(file)
|
64
73
|
return false unless ::FileTest.file?(file)
|
65
74
|
|
@@ -5,8 +5,13 @@ module SlimLint::Filters
|
|
5
5
|
# This is a hack that allows us to access line information directly from the
|
6
6
|
# S-expressions, which makes a lot of other tasks easier.
|
7
7
|
class InjectLineNumbers < Temple::Filter
|
8
|
-
|
8
|
+
# {Sexp} representing a newline.
|
9
|
+
NEWLINE_SEXP = SlimLint::Sexp.new([:newline])
|
9
10
|
|
11
|
+
# Annotates the given {SlimLint::Sexp} with line number information.
|
12
|
+
#
|
13
|
+
# @param sexp [SlimLint::Sexp]
|
14
|
+
# @return [SlimLint::Sexp]
|
10
15
|
def call(sexp)
|
11
16
|
@line_number = 1
|
12
17
|
traverse(sexp)
|
@@ -16,7 +21,7 @@ module SlimLint::Filters
|
|
16
21
|
private
|
17
22
|
|
18
23
|
# Traverses an {Sexp}, annotating it with line numbers by searching for
|
19
|
-
#
|
24
|
+
# newline abstractions within it.
|
20
25
|
#
|
21
26
|
# @param sexp [SlimLint::Sexp]
|
22
27
|
def traverse(sexp)
|
@@ -4,6 +4,10 @@ module SlimLint::Filters
|
|
4
4
|
# These {SlimLint::Sexp}s include additional helpers that makes working with
|
5
5
|
# them more pleasant.
|
6
6
|
class SexpConverter < Temple::Filter
|
7
|
+
# Converts the given {Array} to a {SlimLint::Sexp}.
|
8
|
+
#
|
9
|
+
# @param array_sexp [Array]
|
10
|
+
# @return [SlimLint::Sexp]
|
7
11
|
def call(array_sexp)
|
8
12
|
SlimLint::Sexp.new(array_sexp)
|
9
13
|
end
|
data/lib/slim_lint/lint.rb
CHANGED
@@ -1,7 +1,20 @@
|
|
1
1
|
module SlimLint
|
2
2
|
# Contains information about a problem or issue with a Slim document.
|
3
3
|
class Lint
|
4
|
-
|
4
|
+
# @return [String] file path to which the lint applies
|
5
|
+
attr_reader :filename
|
6
|
+
|
7
|
+
# @return [String] line number of the file the lint corresponds to
|
8
|
+
attr_reader :line
|
9
|
+
|
10
|
+
# @return [SlimLint::Linter] linter that reported the lint
|
11
|
+
attr_reader :linter
|
12
|
+
|
13
|
+
# @return [String] error/warning message to display to user
|
14
|
+
attr_reader :message
|
15
|
+
|
16
|
+
# @return [Symbol] whether this lint is a warning or an error
|
17
|
+
attr_reader :severity
|
5
18
|
|
6
19
|
# Creates a new lint.
|
7
20
|
#
|
@@ -18,6 +31,9 @@ module SlimLint
|
|
18
31
|
@severity = severity
|
19
32
|
end
|
20
33
|
|
34
|
+
# Return whether this lint has a severity of error.
|
35
|
+
#
|
36
|
+
# @return [Boolean]
|
21
37
|
def error?
|
22
38
|
@severity == :error
|
23
39
|
end
|
@@ -5,13 +5,13 @@ module SlimLint
|
|
5
5
|
|
6
6
|
on [:slim, :control] do |sexp|
|
7
7
|
_, _, code = sexp
|
8
|
-
next unless code
|
8
|
+
next unless code[/\A\s*#/]
|
9
9
|
|
10
10
|
comment = code[/\A\s*#(.*\z)/, 1]
|
11
11
|
|
12
12
|
report_lint(sexp,
|
13
13
|
"Slim code comments (`/#{comment}`) are preferred over " \
|
14
|
-
"control statement comments (
|
14
|
+
"control statement comments (`-##{comment}`)")
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -6,27 +6,12 @@ module SlimLint
|
|
6
6
|
|
7
7
|
on [:multi] do |sexp|
|
8
8
|
Utils.for_consecutive_items(sexp,
|
9
|
-
|
9
|
+
->(nested_sexp) { nested_sexp.match?([:slim, :control]) },
|
10
10
|
config['max_consecutive'] + 1) do |group|
|
11
11
|
report_lint(group.first,
|
12
12
|
"#{group.count} consecutive control statements can be " \
|
13
13
|
'merged into a single `ruby:` filter')
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
# Returns whether the given Sexp is a :code abstraction.
|
20
|
-
#
|
21
|
-
# @param sexp [SlimLint::Sexp]
|
22
|
-
# @return [Boolean]
|
23
|
-
def code_sexp?(sexp)
|
24
|
-
# TODO: Switch this with a built-in method on the {Sexp} object itself
|
25
|
-
sexp.is_a?(Sexp) && sexp.match?([:slim, :control])
|
26
|
-
end
|
27
|
-
|
28
|
-
def newline_sexp?(sexp)
|
29
|
-
sexp.is_a?(Sexp) && sexp.first == :newline
|
30
|
-
end
|
31
16
|
end
|
32
17
|
end
|
@@ -6,12 +6,15 @@ module SlimLint
|
|
6
6
|
|
7
7
|
MESSAGE = '`div` is redundant when %s attribute shortcut is present'
|
8
8
|
|
9
|
-
on [:html, :tag, 'div',
|
10
|
-
|
11
|
-
|
9
|
+
on [:html, :tag, 'div',
|
10
|
+
[:html, :attrs,
|
11
|
+
[:html, :attr,
|
12
|
+
capture(:attr_name, anything),
|
13
|
+
[:static]]]] do |sexp|
|
14
|
+
attr = captures[:attr_name]
|
15
|
+
next unless %w[class id].include?(attr)
|
12
16
|
|
13
|
-
|
14
|
-
report_lint(sexp, MESSAGE % 'id')
|
17
|
+
report_lint(sexp, MESSAGE % attr)
|
15
18
|
end
|
16
19
|
end
|
17
20
|
end
|