scss-lint 0.33.0 → 0.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +19 -1
  3. data/data/properties.txt +4 -0
  4. data/lib/scss_lint.rb +1 -0
  5. data/lib/scss_lint/cli.rb +4 -42
  6. data/lib/scss_lint/config.rb +1 -45
  7. data/lib/scss_lint/control_comment_processor.rb +47 -15
  8. data/lib/scss_lint/file_finder.rb +57 -0
  9. data/lib/scss_lint/linter/bang_format.rb +1 -1
  10. data/lib/scss_lint/linter/border_zero.rb +25 -9
  11. data/lib/scss_lint/linter/color_keyword.rb +3 -13
  12. data/lib/scss_lint/linter/color_variable.rb +36 -0
  13. data/lib/scss_lint/linter/declaration_order.rb +2 -2
  14. data/lib/scss_lint/linter/important_rule.rb +12 -0
  15. data/lib/scss_lint/linter/indentation.rb +7 -1
  16. data/lib/scss_lint/linter/property_count.rb +44 -0
  17. data/lib/scss_lint/linter/property_sort_order.rb +73 -19
  18. data/lib/scss_lint/linter/string_quotes.rb +9 -0
  19. data/lib/scss_lint/linter/variable_for_property.rb +20 -0
  20. data/lib/scss_lint/linter/vendor_prefixes.rb +3 -3
  21. data/lib/scss_lint/runner.rb +5 -7
  22. data/lib/scss_lint/utils.rb +34 -0
  23. data/lib/scss_lint/version.rb +1 -1
  24. data/spec/scss_lint/cli_spec.rb +1 -1
  25. data/spec/scss_lint/config_spec.rb +4 -203
  26. data/spec/scss_lint/engine_spec.rb +4 -4
  27. data/spec/scss_lint/file_finder_spec.rb +110 -0
  28. data/spec/scss_lint/linter/bang_format_spec.rb +28 -18
  29. data/spec/scss_lint/linter/border_zero_spec.rb +50 -16
  30. data/spec/scss_lint/linter/color_keyword_spec.rb +16 -16
  31. data/spec/scss_lint/linter/color_variable_spec.rb +102 -0
  32. data/spec/scss_lint/linter/comment_spec.rb +9 -9
  33. data/spec/scss_lint/linter/compass/property_with_mixin_spec.rb +10 -10
  34. data/spec/scss_lint/linter/debug_statement_spec.rb +4 -4
  35. data/spec/scss_lint/linter/declaration_order_spec.rb +80 -80
  36. data/spec/scss_lint/linter/duplicate_property_spec.rb +30 -30
  37. data/spec/scss_lint/linter/else_placement_spec.rb +16 -16
  38. data/spec/scss_lint/linter/empty_line_between_blocks_spec.rb +38 -38
  39. data/spec/scss_lint/linter/empty_rule_spec.rb +4 -4
  40. data/spec/scss_lint/linter/final_newline_spec.rb +6 -6
  41. data/spec/scss_lint/linter/hex_length_spec.rb +16 -16
  42. data/spec/scss_lint/linter/hex_notation_spec.rb +16 -16
  43. data/spec/scss_lint/linter/hex_validation_spec.rb +6 -6
  44. data/spec/scss_lint/linter/id_selector_spec.rb +10 -10
  45. data/spec/scss_lint/linter/import_path_spec.rb +45 -45
  46. data/spec/scss_lint/linter/important_rule_spec.rb +43 -0
  47. data/spec/scss_lint/linter/indentation_spec.rb +103 -43
  48. data/spec/scss_lint/linter/leading_zero_spec.rb +45 -45
  49. data/spec/scss_lint/linter/mergeable_selector_spec.rb +32 -32
  50. data/spec/scss_lint/linter/name_format_spec.rb +75 -41
  51. data/spec/scss_lint/linter/nesting_depth_spec.rb +14 -14
  52. data/spec/scss_lint/linter/placeholder_in_extend_spec.rb +12 -12
  53. data/spec/scss_lint/linter/property_count_spec.rb +104 -0
  54. data/spec/scss_lint/linter/property_sort_order_spec.rb +138 -48
  55. data/spec/scss_lint/linter/property_spelling_spec.rb +14 -14
  56. data/spec/scss_lint/linter/qualifying_element_spec.rb +26 -26
  57. data/spec/scss_lint/linter/selector_depth_spec.rb +26 -26
  58. data/spec/scss_lint/linter/selector_format_spec.rb +114 -114
  59. data/spec/scss_lint/linter/shorthand_spec.rb +32 -32
  60. data/spec/scss_lint/linter/single_line_per_property_spec.rb +10 -10
  61. data/spec/scss_lint/linter/single_line_per_selector_spec.rb +24 -24
  62. data/spec/scss_lint/linter/space_after_comma_spec.rb +60 -60
  63. data/spec/scss_lint/linter/space_after_property_colon_spec.rb +44 -44
  64. data/spec/scss_lint/linter/space_after_property_name_spec.rb +6 -6
  65. data/spec/scss_lint/linter/space_before_brace_spec.rb +119 -119
  66. data/spec/scss_lint/linter/space_between_parens_spec.rb +48 -48
  67. data/spec/scss_lint/linter/string_quotes_spec.rb +74 -62
  68. data/spec/scss_lint/linter/trailing_semicolon_spec.rb +53 -54
  69. data/spec/scss_lint/linter/trailing_zero_spec.rb +34 -34
  70. data/spec/scss_lint/linter/unnecessary_mantissa_spec.rb +10 -10
  71. data/spec/scss_lint/linter/unnecessary_parent_reference_spec.rb +18 -18
  72. data/spec/scss_lint/linter/url_format_spec.rb +4 -4
  73. data/spec/scss_lint/linter/url_quotes_spec.rb +14 -14
  74. data/spec/scss_lint/linter/variable_for_property_spec.rb +115 -0
  75. data/spec/scss_lint/linter/vendor_prefixes_spec.rb +66 -56
  76. data/spec/scss_lint/linter/zero_unit_spec.rb +22 -22
  77. data/spec/scss_lint/linter_spec.rb +72 -28
  78. data/spec/scss_lint/runner_spec.rb +0 -1
  79. data/spec/scss_lint/selector_visitor_spec.rb +23 -23
  80. data/spec/spec_helper.rb +2 -2
  81. metadata +27 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e1109af5e1f6cd4016185ea4d079a4188774f2d3
4
- data.tar.gz: 41389f3cfc6b63b002101e8608b4b2f5eda48ec6
3
+ metadata.gz: d9d896111a17640c23d31de062d34329fd52a56a
4
+ data.tar.gz: 6f34a07866cd4b7c77a18137f2bec2570e9f31e5
5
5
  SHA512:
6
- metadata.gz: 9120548dc3302ae1c7922e4b46166d6fa9238f4bf755824abad0176dec920d4722c0b4b67042258c7f73ed99903785a4f8bde6e6471809093c74cf0a7f0fbfbb
7
- data.tar.gz: 046761b2017cbb1f333e446cae0fda6fb52a5fd8055764e91797b3eccb9eddcf862a18597c7a24ebe5dcd2e6f76d40397b6144298bbcd78d5a4574c88fe774d3
6
+ metadata.gz: d100dfddcde8591cbff342d88bc37f685ac337eb32b9193b87f09c46cf50dc61c04717a4e67fe843f6135fb186f8c5e43b7c6711680d67e766e56e71bb04ff94
7
+ data.tar.gz: 65e67618d2e00e428a38adb5243236dea6464f17df75914937da60afbf3eded4f3b0e7be4d526b4d05c264c9a0ef5f5ed1cc13cbab3dac4a5cde3673f4e991d0
@@ -10,10 +10,14 @@ linters:
10
10
 
11
11
  BorderZero:
12
12
  enabled: true
13
+ convention: zero # or `none`
13
14
 
14
15
  ColorKeyword:
15
16
  enabled: true
16
17
 
18
+ ColorVariable:
19
+ enabled: true
20
+
17
21
  Comment:
18
22
  enabled: true
19
23
 
@@ -55,6 +59,9 @@ linters:
55
59
  IdSelector:
56
60
  enabled: true
57
61
 
62
+ ImportantRule:
63
+ enabled: true
64
+
58
65
  ImportPath:
59
66
  enabled: true
60
67
  leading_underscore: false
@@ -62,6 +69,7 @@ linters:
62
69
 
63
70
  Indentation:
64
71
  enabled: true
72
+ allow_non_nested_indentation: false
65
73
  character: space # or 'tab'
66
74
  width: 2
67
75
 
@@ -85,9 +93,15 @@ linters:
85
93
  PlaceholderInExtend:
86
94
  enabled: true
87
95
 
96
+ PropertyCount:
97
+ enabled: false
98
+ include_nested: false
99
+ max_properties: 10
100
+
88
101
  PropertySortOrder:
89
102
  enabled: true
90
103
  ignore_unspecified: false
104
+ separate_groups: false
91
105
 
92
106
  PropertySpelling:
93
107
  enabled: true
@@ -129,7 +143,7 @@ linters:
129
143
 
130
144
  SpaceBeforeBrace:
131
145
  enabled: true
132
- style: space
146
+ style: space # or 'new_line'
133
147
  allow_single_line_padding: false
134
148
 
135
149
  SpaceBetweenParens:
@@ -158,6 +172,10 @@ linters:
158
172
  UrlQuotes:
159
173
  enabled: true
160
174
 
175
+ VariableForProperty:
176
+ enabled: false
177
+ properties: []
178
+
161
179
  VendorPrefixes:
162
180
  enabled: true
163
181
  identifier_list: base
@@ -102,6 +102,9 @@ box-pack
102
102
  box-reflect
103
103
  box-shadow
104
104
  box-sizing
105
+ break-after
106
+ break-before
107
+ break-inside
105
108
  buffered-rendering
106
109
  caption-side
107
110
  clear
@@ -418,6 +421,7 @@ text-overline-style
418
421
  text-overline-width
419
422
  text-rendering
420
423
  text-security
424
+ text-size-adjust
421
425
  text-shadow
422
426
  text-stroke
423
427
  text-stroke-color
@@ -5,6 +5,7 @@ require 'scss_lint/engine'
5
5
  require 'scss_lint/location'
6
6
  require 'scss_lint/lint'
7
7
  require 'scss_lint/linter_registry'
8
+ require 'scss_lint/file_finder'
8
9
  require 'scss_lint/runner'
9
10
  require 'scss_lint/selector_visitor'
10
11
  require 'scss_lint/control_comment_processor'
@@ -1,4 +1,3 @@
1
- require 'find'
2
1
  require 'rainbow'
3
2
  require 'rainbow/ext/string'
4
3
  require 'scss_lint/options'
@@ -49,7 +48,7 @@ module SCSSLint
49
48
 
50
49
  def scan_for_lints(options, config)
51
50
  runner = Runner.new(config)
52
- runner.run(files_to_lint(options, config))
51
+ runner.run(FileFinder.new(config).find(options[:files]))
53
52
  report_lints(options, runner.lints)
54
53
 
55
54
  if runner.lints.any?(&:error?)
@@ -87,9 +86,9 @@ module SCSSLint
87
86
  def setup_configuration(options)
88
87
  config =
89
88
  if options[:config_file]
90
- Config.load(options[:config_file]).tap do |conf|
91
- conf.preferred = true
92
- end
89
+ Config.load(options[:config_file])
90
+ elsif File.exist?(Config::FILE_NAME)
91
+ Config.load(Config::FILE_NAME)
93
92
  else
94
93
  Config.default
95
94
  end
@@ -123,43 +122,6 @@ module SCSSLint
123
122
  config
124
123
  end
125
124
 
126
- def files_to_lint(options, config)
127
- if options[:files].empty?
128
- options[:files] = config.scss_files
129
- end
130
-
131
- extract_files_from(options[:files]).reject do |file|
132
- actual_config =
133
- if !config.preferred && (config_for_file = Config.for_file(file))
134
- merge_options_with_config(options, config_for_file.dup)
135
- else
136
- config
137
- end
138
-
139
- actual_config.excluded_file?(file)
140
- end
141
- end
142
-
143
- # @param list [Array]
144
- def extract_files_from(list)
145
- files = []
146
- list.each do |file|
147
- Find.find(file) do |f|
148
- files << f if scssish_file?(f)
149
- end
150
- end
151
- files.uniq
152
- end
153
-
154
- VALID_EXTENSIONS = %w[.css .scss]
155
- # @param file [String]
156
- # @return [Boolean]
157
- def scssish_file?(file)
158
- return false unless FileTest.file?(file)
159
-
160
- VALID_EXTENSIONS.include?(File.extname(file))
161
- end
162
-
163
125
  # @param options [Hash]
164
126
  # @param lints [Array<Lint>]
165
127
  def report_lints(options, lints)
@@ -1,4 +1,3 @@
1
- require 'pathname'
2
1
  require 'yaml'
3
2
 
4
3
  module SCSSLint
@@ -7,7 +6,6 @@ module SCSSLint
7
6
  FILE_NAME = '.scss-lint.yml'
8
7
  DEFAULT_FILE = File.join(SCSS_LINT_HOME, 'config', 'default.yml')
9
8
 
10
- attr_accessor :preferred # If this config should be preferred over others
11
9
  attr_reader :options, :warnings
12
10
 
13
11
  class << self
@@ -27,17 +25,6 @@ module SCSSLint
27
25
  Config.new(config_options)
28
26
  end
29
27
 
30
- # Loads the configuration for a given file.
31
- def for_file(file_path)
32
- directory = File.dirname(File.expand_path(file_path))
33
- @dir_to_config ||= {}
34
- @dir_to_config[directory] ||=
35
- begin
36
- config_file = possible_config_files(directory).find(&:file?)
37
- Config.load(config_file.to_s) if config_file
38
- end
39
- end
40
-
41
28
  def linter_name(linter)
42
29
  linter = linter.is_a?(Class) ? linter : linter.class
43
30
  linter.name.split('::')[2..-1].join('::')
@@ -45,13 +32,6 @@ module SCSSLint
45
32
 
46
33
  private
47
34
 
48
- def possible_config_files(directory)
49
- files = Pathname.new(directory)
50
- .enum_for(:ascend)
51
- .map { |path| path + FILE_NAME }
52
- files << Pathname.new(FILE_NAME)
53
- end
54
-
55
35
  def default_options_hash
56
36
  @default_options_hash ||= load_options_hash_from_file(DEFAULT_FILE)
57
37
  end
@@ -74,7 +54,6 @@ module SCSSLint
74
54
  end
75
55
 
76
56
  options = convert_single_options_to_arrays(options)
77
- options = extend_inherited_configs(options, file)
78
57
  options = merge_wildcard_linter_options(options)
79
58
  options = ensure_exclude_paths_are_absolute(options, file)
80
59
  options = ensure_linter_exclude_paths_are_absolute(options, file)
@@ -88,31 +67,13 @@ module SCSSLint
88
67
 
89
68
  if options['exclude']
90
69
  # Ensure exclude is an array, since we allow user to specify a single
91
- # string. We do this before merging with the config loaded via
92
- # inherit_from since this allows us to merge the excludes from that,
93
- # rather than overwriting them.
70
+ # string.
94
71
  options['exclude'] = [options['exclude']].flatten
95
72
  end
96
73
 
97
74
  options
98
75
  end
99
76
 
100
- # Loads and extends a list of inherited options with the given options.
101
- def extend_inherited_configs(options, original_file)
102
- return options unless options['inherit_from']
103
- options = options.dup
104
-
105
- includes = [options.delete('inherit_from')].flatten.map do |include_file|
106
- load_options_hash_from_file(path_relative_to_config(include_file, original_file))
107
- end
108
-
109
- merged_includes = includes[1..-1].inject(includes.first) do |merged, include_file|
110
- smart_merge(merged, include_file)
111
- end
112
-
113
- smart_merge(merged_includes, options)
114
- end
115
-
116
77
  # Merge options from wildcard linters into individual linter configs
117
78
  def merge_wildcard_linter_options(options)
118
79
  options = options.dup
@@ -211,11 +172,6 @@ module SCSSLint
211
172
  validate_linters
212
173
  end
213
174
 
214
- def ==(other)
215
- super || @options == other.options
216
- end
217
- alias_method :eql?, :==
218
-
219
175
  def enabled_linters
220
176
  LinterRegistry.extract_linters_from(@options['linters'].keys).select do |linter|
221
177
  linter_options(linter)['enabled']
@@ -20,25 +20,18 @@ module SCSSLint
20
20
  #
21
21
  # @param node [Sass::Tree::Node]
22
22
  def before_node_visit(node)
23
- return unless node.is_a?(Sass::Tree::CommentNode)
23
+ return unless command = extract_command(node)
24
24
 
25
- return unless match = /(?x)
26
- \*\s* # Comment line start marker
27
- scss-lint:
28
- (?<command>disable|enable)\s+
29
- (?<linters>.*?)
30
- \s*(?:\*\/|\n) # Comment end marker or end of line
31
- /.match(node.value.first)
32
-
33
- linters = match[:linters].split(/\s*,\s*|\s+/)
25
+ linters = command[:linters]
34
26
  return unless linters.include?('all') || linters.include?(@linter.name)
35
27
 
36
- process_command(match[:command], node)
28
+ process_command(command[:action], node)
37
29
 
38
30
  # Is the control comment the only thing on this line?
39
- return if %r{^\s*(//|/\*)}.match(@linter.engine.lines[node.line - 1])
31
+ return if node.is_a?(Sass::Tree::RuleNode) ||
32
+ %r{^\s*(//|/\*)}.match(@linter.engine.lines[node.line - 1])
40
33
 
41
- # If so, pop since we only want the comment to apply to the single line
34
+ # Otherwise, pop since we only want comment to apply to the single line
42
35
  pop_control_comment_stack(node)
43
36
  end
44
37
 
@@ -53,6 +46,29 @@ module SCSSLint
53
46
 
54
47
  private
55
48
 
49
+ def extract_command(node)
50
+ comment =
51
+ case node
52
+ when Sass::Tree::CommentNode
53
+ node.value.first
54
+ when Sass::Tree::RuleNode
55
+ node.rule.select { |chunk| chunk.is_a?(String) }.join
56
+ end
57
+
58
+ return unless match = %r{
59
+ (/|\*)\s* # Comment start marker
60
+ scss-lint:
61
+ (?<action>disable|enable)\s+
62
+ (?<linters>.*?)
63
+ \s*(?:\*/|\n) # Comment end marker or end of line
64
+ }x.match(comment)
65
+
66
+ {
67
+ action: match[:action],
68
+ linters: match[:linters].split(/\s*,\s*|\s+/),
69
+ }
70
+ end
71
+
56
72
  def process_command(command, node)
57
73
  case command
58
74
  when 'disable'
@@ -71,13 +87,29 @@ module SCSSLint
71
87
  # apply (if it is a control comment enable node, it will be the line of
72
88
  # the comment itself).
73
89
  child = node
74
- while child.children.last.is_a?(Sass::Tree::Node)
75
- child = child.children.last
90
+ prev_child = node
91
+ while (child = last_child(child)) != prev_child
92
+ prev_child = child
76
93
  end
77
94
 
78
95
  end_line = child.line
79
96
 
80
97
  @disabled_lines.merge(start_line..end_line)
81
98
  end
99
+
100
+ # Gets the child of the node that resides on the lowest line in the file.
101
+ #
102
+ # This is necessary due to the fact that our monkey patching of the parse
103
+ # tree's {#children} method does not return nodes sorted by their line
104
+ # number.
105
+ #
106
+ # @param node [Sass::Tree::Node, Sass::Script::Tree::Node]
107
+ # @return [Sass::Tree::Node, Sass::Script::Tree::Node]
108
+ def last_child(node)
109
+ node.children.inject(node) do |lowest, child|
110
+ return lowest unless child.respond_to?(:line)
111
+ lowest.line < child.line ? child : lowest
112
+ end
113
+ end
82
114
  end
83
115
  end
@@ -0,0 +1,57 @@
1
+ require 'find'
2
+
3
+ module SCSSLint
4
+ # Finds all SCSS files that should be linted given a set of paths, globs, and
5
+ # configuration.
6
+ class FileFinder
7
+ # List of extensions of files to include when only a directory is specified
8
+ # as a path.
9
+ VALID_EXTENSIONS = %w[.css .scss]
10
+
11
+ # Create a {FileFinder}.
12
+ #
13
+ # @param config [SCSSLint::Config]
14
+ def initialize(config)
15
+ @config = config
16
+ end
17
+
18
+ # Find all files that match given the specified options.
19
+ #
20
+ # @param patterns [Array<String>] a list of file paths and glob patterns
21
+ def find(patterns)
22
+ # If no explicit patterns given, use patterns listed in config
23
+ patterns = @config.scss_files if patterns.empty?
24
+
25
+ extract_files_from(patterns).reject do |file|
26
+ @config.excluded_file?(file)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ # @param list [Array]
33
+ def extract_files_from(list)
34
+ files = []
35
+
36
+ list.each do |file|
37
+ if File.directory?(file)
38
+ Find.find(file) do |f|
39
+ files << f if scssish_file?(f)
40
+ end
41
+ else
42
+ files << file # Otherwise include file as-is
43
+ end
44
+ end
45
+
46
+ files.uniq
47
+ end
48
+
49
+ # @param file [String]
50
+ # @return [true,false]
51
+ def scssish_file?(file)
52
+ return false unless FileTest.file?(file)
53
+
54
+ VALID_EXTENSIONS.include?(File.extname(file))
55
+ end
56
+ end
57
+ end
@@ -6,7 +6,7 @@ module SCSSLint
6
6
  STOPPING_CHARACTERS = ['!', "'", '"', nil]
7
7
 
8
8
  def visit_prop(node)
9
- return unless node.to_sass.include?('!')
9
+ return unless source_from_range(node.source_range).include?('!')
10
10
  return unless check_spacing(node)
11
11
 
12
12
  before_qualifier = config['space_before_bang'] ? '' : 'not '
@@ -1,16 +1,12 @@
1
1
  module SCSSLint
2
- # Reports when `border: 0` can be used instead of `border: none`.
2
+ # Enforce a particular value for empty borders.
3
3
  class Linter::BorderZero < Linter
4
4
  include LinterRegistry
5
5
 
6
- def visit_prop(node)
7
- return unless BORDER_PROPERTIES.include?(node.name.first.to_s)
8
- return unless node.value.to_sass.strip == 'none'
9
-
10
- add_lint(node, '`border: 0;` is preferred over `border: none;`')
11
- end
12
-
13
- private
6
+ CONVENTION_TO_PREFERENCE = {
7
+ 'zero' => %w[0 none],
8
+ 'none' => %w[none 0],
9
+ }
14
10
 
15
11
  BORDER_PROPERTIES = %w[
16
12
  border
@@ -19,5 +15,25 @@ module SCSSLint
19
15
  border-bottom
20
16
  border-left
21
17
  ]
18
+
19
+ def visit_root(_node)
20
+ @preference = CONVENTION_TO_PREFERENCE[config['convention']]
21
+ yield # Continue linting children
22
+ end
23
+
24
+ def visit_prop(node)
25
+ return unless BORDER_PROPERTIES.include?(node.name.first.to_s)
26
+ check_border(node, node.value.to_sass.strip)
27
+ end
28
+
29
+ private
30
+
31
+ def check_border(node, border)
32
+ return unless %w[0 none].include?(border)
33
+ return if @preference[0] == border
34
+
35
+ add_lint(node, "`border: #{@preference[0]} is preferred over " \
36
+ "`border: #{@preference[1]}`")
37
+ end
22
38
  end
23
39
  end