slim_lint 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|