scss-lint 0.29.0 → 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +31 -1
  3. data/data/prefixed-identifiers/base.txt +107 -0
  4. data/data/prefixed-identifiers/bourbon.txt +71 -0
  5. data/lib/scss_lint/cli.rb +34 -7
  6. data/lib/scss_lint/config.rb +12 -1
  7. data/lib/scss_lint/engine.rb +3 -1
  8. data/lib/scss_lint/linter/bang_format.rb +40 -0
  9. data/lib/scss_lint/linter/declaration_order.rb +35 -14
  10. data/lib/scss_lint/linter/import_path.rb +62 -0
  11. data/lib/scss_lint/linter/name_format.rb +1 -1
  12. data/lib/scss_lint/linter/nesting_depth.rb +24 -0
  13. data/lib/scss_lint/linter/property_sort_order.rb +4 -11
  14. data/lib/scss_lint/linter/property_spelling.rb +25 -8
  15. data/lib/scss_lint/linter/qualifying_element.rb +42 -0
  16. data/lib/scss_lint/linter/selector_format.rb +23 -11
  17. data/lib/scss_lint/linter/space_after_property_colon.rb +4 -4
  18. data/lib/scss_lint/linter/space_after_property_name.rb +16 -1
  19. data/lib/scss_lint/linter/space_before_brace.rb +36 -9
  20. data/lib/scss_lint/linter/trailing_semicolon.rb +6 -2
  21. data/lib/scss_lint/linter/vendor_prefixes.rb +64 -0
  22. data/lib/scss_lint/rake_task.rb +1 -0
  23. data/lib/scss_lint/runner.rb +2 -1
  24. data/lib/scss_lint/sass/script.rb +10 -0
  25. data/lib/scss_lint/version.rb +1 -1
  26. data/spec/scss_lint/cli_spec.rb +45 -2
  27. data/spec/scss_lint/linter/bang_format_spec.rb +79 -0
  28. data/spec/scss_lint/linter/declaration_order_spec.rb +466 -0
  29. data/spec/scss_lint/linter/import_path_spec.rb +300 -0
  30. data/spec/scss_lint/linter/nesting_depth_spec.rb +114 -0
  31. data/spec/scss_lint/linter/property_spelling_spec.rb +27 -0
  32. data/spec/scss_lint/linter/qualifying_element_spec.rb +125 -0
  33. data/spec/scss_lint/linter/selector_format_spec.rb +329 -0
  34. data/spec/scss_lint/linter/space_after_property_colon_spec.rb +14 -0
  35. data/spec/scss_lint/linter/space_after_property_name_spec.rb +14 -0
  36. data/spec/scss_lint/linter/space_before_brace_spec.rb +401 -17
  37. data/spec/scss_lint/linter/trailing_semicolon_spec.rb +47 -0
  38. data/spec/scss_lint/linter/vendor_prefixes_spec.rb +350 -0
  39. metadata +19 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fe908b2051342dce6151d04897242d18a3f9bf0b
4
- data.tar.gz: 785752360ba0475386ecc1c8f8d202268293f428
3
+ metadata.gz: 63daef837600174964f65634095dac45b13a1bb2
4
+ data.tar.gz: 8b8e1fdebc6d40d577c02a0092c092212f0d46ee
5
5
  SHA512:
6
- metadata.gz: cd5a9205186a67350c31d52d0cbd8677510fa5ae270ca0b25fc99dd87768c8b02393ebbbf2d068cc66f19ddf89ff7fb19b467d74a99ea3702fe7ff134352ef0a
7
- data.tar.gz: 8063dfc1815c2d3b4a3edb76540635282484e0e56a1ce7b71bf2a60ec493ad5b3948b17b4fae56db6e1df226aa6704802946530aaf6304bd3a5952875b6d1409
6
+ metadata.gz: 6ed4cbb25cacdfdf0645ea476ccc86032ae8032c22fdb1b3cf8fdd6934601bdc075aa4ed71fa1a05d2dec08785ceb591d53eeb2c6541b5567f434ff4ce161d20
7
+ data.tar.gz: 07a85bc0d214819857e179babe4b465193adf01391f7eecadd25611264c28605e0990911e1eb6dd75ed79b65b0cb50b6529187513e6842674e5c5abeafeca5f2
@@ -1,5 +1,13 @@
1
1
  # Default application configuration that all configurations inherit from.
2
+
3
+ scss_files: "**/*.scss"
4
+
2
5
  linters:
6
+ BangFormat:
7
+ enabled: true
8
+ space_before_bang: true
9
+ space_after_bang: false
10
+
3
11
  BorderZero:
4
12
  enabled: true
5
13
 
@@ -47,6 +55,11 @@ linters:
47
55
  IdWithExtraneousSelector:
48
56
  enabled: true
49
57
 
58
+ ImportPath:
59
+ enabled: true
60
+ leading_underscore: false
61
+ filename_extension: false
62
+
50
63
  Indentation:
51
64
  enabled: true
52
65
  character: space # or 'tab'
@@ -64,6 +77,10 @@ linters:
64
77
  enabled: true
65
78
  convention: hyphenated_lowercase # or 'BEM', or a regex pattern
66
79
 
80
+ NestingDepth:
81
+ enabled: true
82
+ max_depth: 3
83
+
67
84
  PlaceholderInExtend:
68
85
  enabled: true
69
86
 
@@ -75,13 +92,19 @@ linters:
75
92
  enabled: true
76
93
  extra_properties: []
77
94
 
95
+ QualifyingElement:
96
+ enabled: true
97
+ allow_with_attribute: false
98
+ allow_with_class: false
99
+ allow_with_id: false
100
+
78
101
  SelectorDepth:
79
102
  enabled: true
80
103
  max_depth: 3
81
104
 
82
105
  SelectorFormat:
83
106
  enabled: true
84
- convention: hyphenated_lowercase # or 'snake_case', or 'camel_case', or a regex pattern
107
+ convention: hyphenated_lowercase # or 'BEM', or 'snake_case', or 'camel_case', or a regex pattern
85
108
 
86
109
  Shorthand:
87
110
  enabled: true
@@ -105,6 +128,7 @@ linters:
105
128
 
106
129
  SpaceBeforeBrace:
107
130
  enabled: true
131
+ style: space
108
132
  allow_single_line_padding: false
109
133
 
110
134
  SpaceBetweenParens:
@@ -133,6 +157,12 @@ linters:
133
157
  UrlQuotes:
134
158
  enabled: true
135
159
 
160
+ VendorPrefixes:
161
+ enabled: true
162
+ identifier_list: base
163
+ include: []
164
+ exclude: []
165
+
136
166
  ZeroUnit:
137
167
  enabled: true
138
168
 
@@ -0,0 +1,107 @@
1
+ # Based on Autoprefixer --info
2
+
3
+ # At-Rules
4
+ keyframes
5
+
6
+ # Selectors
7
+ selection
8
+ placeholder
9
+ fullscreen
10
+
11
+ # Properties
12
+ transition
13
+ transition-property
14
+ border-radius
15
+ border-top-left-radius
16
+ border-top-right-radius
17
+ border-bottom-right-radius
18
+ border-bottom-left-radius
19
+ box-shadow
20
+ animation
21
+ animation-name
22
+ animation-duration
23
+ animation-delay
24
+ animation-direction
25
+ animation-fill-mode
26
+ animation-iteration-count
27
+ animation-play-state
28
+ animation-timing-function
29
+ transition-duration
30
+ transition-delay
31
+ transition-timing-function
32
+ transform
33
+ transform-origin
34
+ perspective
35
+ perspective-origin
36
+ transform-style
37
+ backface-visibility
38
+ border-image
39
+ box-sizing
40
+ filter
41
+ columns
42
+ column-width
43
+ column-gap
44
+ column-rule
45
+ column-rule-color
46
+ column-rule-width
47
+ column-count
48
+ column-rule-style
49
+ column-span
50
+ column-fill
51
+ break-before
52
+ break-after
53
+ break-inside
54
+ user-select
55
+ flex
56
+ flex-grow
57
+ flex-shrink
58
+ flex-basis
59
+ flex-direction
60
+ flex-wrap
61
+ flex-flow
62
+ justify-content
63
+ order
64
+ align-items
65
+ align-self
66
+ align-content
67
+ background-clip
68
+ background-origin
69
+ background-size
70
+ font-feature-settings
71
+ font-variant-ligatures
72
+ font-language-override
73
+ font-kerning
74
+ hyphens
75
+ tab-size
76
+ touch-action
77
+ text-decoration-style
78
+ text-decoration-line
79
+ text-decoration-color
80
+ text-size-adjust
81
+ clip-path
82
+ mask
83
+ mask-clip
84
+ mask-composite
85
+ mask-image
86
+ mask-origin
87
+ mask-position
88
+ mask-repeat
89
+ mask-size
90
+
91
+ # Values
92
+ linear-gradient
93
+ repeating-linear-gradient
94
+ radial-gradient
95
+ repeating-radial-gradient
96
+ flex
97
+ inline-flex
98
+ calc
99
+ max-content
100
+ min-content
101
+ fit-content
102
+ fill-available
103
+ zoom-in
104
+ zoom-out
105
+ grab
106
+ grabbing
107
+ sticky
@@ -0,0 +1,71 @@
1
+ # Identifiers covered by Bourbon mixins
2
+
3
+ # At-Rules
4
+ keyframes
5
+
6
+ # Selectors
7
+ placeholder
8
+
9
+ # Properties
10
+ animation
11
+ animation-delay
12
+ animation-direction
13
+ animation-duration
14
+ animation-fill-mode
15
+ animation-iteration-count
16
+ animation-name
17
+ animation-play-state
18
+ animation-timing-function
19
+ appearance
20
+ backface-visibility
21
+ background
22
+ border-image
23
+ border-top-left-radius
24
+ border-top-right-radius
25
+ border-bottom-right-radius
26
+ border-bottom-left-radius
27
+ box-sizing
28
+ calc
29
+ columns
30
+ column-width
31
+ column-gap
32
+ column-rule
33
+ column-rule-color
34
+ column-rule-width
35
+ column-count
36
+ column-rule-style
37
+ column-span
38
+ column-fill
39
+ filter
40
+ flex
41
+ flex-grow
42
+ flex-shrink
43
+ flex-basis
44
+ flex-direction
45
+ flex-wrap
46
+ flex-flow
47
+ justify-content
48
+ order
49
+ align-items
50
+ align-self
51
+ align-content
52
+ font-feature-settings
53
+ hyphens
54
+ perspective
55
+ perspective-origin
56
+ transform
57
+ transform-origin
58
+ transform-style
59
+ transition
60
+ transition-property
61
+ transition-duration
62
+ transition-delay
63
+ transition-timing-function
64
+ user-select
65
+
66
+
67
+ # Values
68
+ crisp-edges
69
+ optimize-contrast
70
+ linear-gradient
71
+ radial-gradient
@@ -20,10 +20,12 @@ module SCSSLint
20
20
  config: 78, # Configuration error
21
21
  }
22
22
 
23
+ DEFAULT_REPORTER = [SCSSLint::Reporter::DefaultReporter, :stdout]
24
+
23
25
  # @param args [Array]
24
26
  def initialize(args = [])
25
27
  @args = args
26
- @options = {}
28
+ @options = { reporters: [DEFAULT_REPORTER] }
27
29
  @config = Config.default
28
30
  end
29
31
 
@@ -32,7 +34,13 @@ module SCSSLint
32
34
  options_parser.parse!(@args)
33
35
 
34
36
  # Take the rest of the arguments as files/directories
35
- @options[:files] = @args
37
+
38
+ if @args.empty?
39
+ @options[:files] = @config.scss_files
40
+ else
41
+ @options[:files] = @args
42
+ end
43
+
36
44
  rescue OptionParser::InvalidOption => ex
37
45
  print_help options_parser.help, ex
38
46
  end
@@ -66,6 +74,14 @@ module SCSSLint
66
74
  define_output_format(format)
67
75
  end
68
76
 
77
+ opts.on('-o', '--out path', 'Write output to a file instead of STDOUT', String) do |path|
78
+ define_output_path(path)
79
+ end
80
+
81
+ opts.on('-r', '--require path', 'Require Ruby file', String) do |path|
82
+ require path
83
+ end
84
+
69
85
  opts.on_tail('--show-formatters', 'Shows available formatters') do
70
86
  print_formatters
71
87
  end
@@ -192,20 +208,31 @@ module SCSSLint
192
208
  # @param lints [Array<Lint>]
193
209
  def report_lints(lints)
194
210
  sorted_lints = lints.sort_by { |l| [l.filename, l.location] }
195
- reporter = @options.fetch(:reporter, SCSSLint::Reporter::DefaultReporter)
196
- .new(sorted_lints)
197
- output = reporter.report_lints
198
- print output if output
211
+ @options.fetch(:reporters).each do |reporter, output|
212
+ results = reporter.new(sorted_lints).report_lints
213
+ io = (output == :stdout ? $stdout : File.new(output, 'w+'))
214
+ io.print results if results
215
+ end
199
216
  end
200
217
 
201
218
  # @param format [String]
202
219
  def define_output_format(format)
203
- @options[:reporter] = SCSSLint::Reporter.const_get(format + 'Reporter')
220
+ unless @options[:reporters] == [DEFAULT_REPORTER] && format == 'Default'
221
+ @options[:reporters].reject! { |i| i == DEFAULT_REPORTER }
222
+ reporter = SCSSLint::Reporter.const_get(format + 'Reporter')
223
+ @options[:reporters] << [reporter, :stdout]
224
+ end
204
225
  rescue NameError
205
226
  puts "Invalid output format specified: #{format}"
206
227
  halt :config
207
228
  end
208
229
 
230
+ # @param path [String]
231
+ def define_output_path(path)
232
+ last_reporter, _output = @options[:reporters].pop
233
+ @options[:reporters] << [last_reporter, path]
234
+ end
235
+
209
236
  def print_formatters
210
237
  puts 'Installed formatters:'
211
238
 
@@ -175,7 +175,9 @@ module SCSSLint
175
175
  if relative_include_path.start_with?('/')
176
176
  relative_include_path
177
177
  else
178
- File.join(File.dirname(base_config_path), relative_include_path)
178
+ path = File.join(File.dirname(base_config_path), relative_include_path)
179
+ # Remove double backslashes appearing in Windows paths.
180
+ path.gsub(%r{^//}, File::SEPARATOR)
179
181
  end
180
182
  end
181
183
 
@@ -262,6 +264,15 @@ module SCSSLint
262
264
  @options['exclude'] << abs_path
263
265
  end
264
266
 
267
+ # @return Array
268
+ def scss_files
269
+ if path = @options['scss_files']
270
+ Dir[path]
271
+ else
272
+ []
273
+ end
274
+ end
275
+
265
276
  private
266
277
 
267
278
  def validate_linters
@@ -20,7 +20,9 @@ module SCSSLint
20
20
  @contents = scss_or_filename
21
21
  end
22
22
 
23
- @lines = @contents.lines.to_a # Need `to_a` for Ruby 1.9.3
23
+ # Need to force encoding to avoid Windows-related bugs.
24
+ # Need `to_a` for Ruby 1.9.3.
25
+ @lines = @contents.force_encoding('UTF-8').lines.to_a
24
26
  @tree = @engine.to_tree
25
27
  rescue Encoding::UndefinedConversionError, Sass::SyntaxError => error
26
28
  if error.is_a?(Encoding::UndefinedConversionError) ||
@@ -0,0 +1,40 @@
1
+ module SCSSLint
2
+ # Checks spacing of ! declarations, like !important and !default
3
+ class Linter::BangFormat < Linter
4
+ include LinterRegistry
5
+
6
+ def visit_prop(node)
7
+ return unless node.to_sass.include?('!')
8
+ return unless check_spacing(node)
9
+
10
+ before_qualifier = config['space_before_bang'] ? '' : 'not '
11
+ after_qualifier = config['space_after_bang'] ? '' : 'not '
12
+
13
+ add_lint(node, "! should #{before_qualifier}be preceeded by a space, " \
14
+ "and should #{after_qualifier}be followed by a space")
15
+ end
16
+
17
+ private
18
+
19
+ def find_bang_offset(range)
20
+ offset = 0
21
+ offset += 1 while character_at(range.start_pos, offset) != '!'
22
+ offset
23
+ end
24
+
25
+ def check_spacing(node)
26
+ range = node.value_source_range
27
+ offset = find_bang_offset(range)
28
+
29
+ before_expected = config['space_before_bang'] ? / / : /[^ ]/
30
+ before_actual = character_at(range.start_pos, offset - 1)
31
+ before_is_wrong = (before_actual =~ before_expected).nil?
32
+
33
+ after_expected = config['space_after_bang'] ? / / : /[^ ]/
34
+ after_actual = character_at(range.start_pos, offset + 1)
35
+ after_is_wrong = (after_actual =~ after_expected).nil?
36
+
37
+ before_is_wrong || after_is_wrong
38
+ end
39
+ end
40
+ end
@@ -3,35 +3,56 @@ module SCSSLint
3
3
  class Linter::DeclarationOrder < Linter
4
4
  include LinterRegistry
5
5
 
6
+ def check_order(node)
7
+ check_node(node)
8
+ yield # Continue linting children
9
+ end
10
+
11
+ alias_method :visit_rule, :check_order
12
+ alias_method :visit_mixin, :check_order
13
+
14
+ private
15
+
16
+ MESSAGE =
17
+ 'Rule sets should be ordered as follows: '\
18
+ '@extends, @includes without @content, ' \
19
+ 'properties, @includes with @content, ' \
20
+ 'nested rule sets'
21
+
22
+ MIXIN_WITH_CONTENT = 'mixin_with_content'
23
+
6
24
  DECLARATION_ORDER = [
7
25
  Sass::Tree::ExtendNode,
26
+ Sass::Tree::MixinNode,
8
27
  Sass::Tree::PropNode,
28
+ MIXIN_WITH_CONTENT,
9
29
  Sass::Tree::RuleNode,
10
30
  ]
11
31
 
12
- def visit_rule(node)
32
+ def important_node?(node)
33
+ DECLARATION_ORDER.include?(node.class)
34
+ end
35
+
36
+ def check_node(node)
13
37
  children = node.children.select { |n| important_node?(n) }
14
- .map(&:class)
38
+ .map { |n| node_declaration_type(n) }
15
39
 
16
40
  sorted_children = children.sort do |a, b|
17
41
  DECLARATION_ORDER.index(a) <=> DECLARATION_ORDER.index(b)
18
42
  end
19
43
 
20
- if children != sorted_children
21
- add_lint(node.children.first, MESSAGE)
22
- end
23
-
24
- yield # Continue linting children
44
+ return unless children != sorted_children
45
+ add_lint(node.children.first, MESSAGE)
25
46
  end
26
47
 
27
- private
28
-
29
- MESSAGE =
30
- 'Rule sets should start with @extend declarations, followed by ' \
31
- 'properties and nested rule sets, in that order'
48
+ def node_declaration_type(node)
49
+ # If the node has no children, return the class.
50
+ return node.class unless node.has_children
32
51
 
33
- def important_node?(node)
34
- DECLARATION_ORDER.include? node.class
52
+ # If the node is a mixin with children, indicate that;
53
+ # otherwise, just return the class.
54
+ return node.class unless node.is_a?(Sass::Tree::MixinNode)
55
+ MIXIN_WITH_CONTENT
35
56
  end
36
57
  end
37
58
  end