scss_lint 0.43.2 → 0.44.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/bin/scss-lint +2 -1
  3. data/config/default.yml +3 -0
  4. data/data/properties.txt +35 -0
  5. data/data/property-sort-orders/smacss.txt +9 -0
  6. data/data/pseudo-elements.txt +5 -0
  7. data/lib/scss_lint.rb +1 -0
  8. data/lib/scss_lint/cli.rb +50 -30
  9. data/lib/scss_lint/config.rb +28 -3
  10. data/lib/scss_lint/constants.rb +6 -4
  11. data/lib/scss_lint/control_comment_processor.rb +8 -7
  12. data/lib/scss_lint/engine.rb +1 -1
  13. data/lib/scss_lint/file_finder.rb +1 -1
  14. data/lib/scss_lint/linter.rb +8 -7
  15. data/lib/scss_lint/linter/bang_format.rb +2 -2
  16. data/lib/scss_lint/linter/border_zero.rb +2 -2
  17. data/lib/scss_lint/linter/color_variable.rb +1 -1
  18. data/lib/scss_lint/linter/declaration_order.rb +10 -8
  19. data/lib/scss_lint/linter/duplicate_property.rb +2 -2
  20. data/lib/scss_lint/linter/empty_line_between_blocks.rb +3 -1
  21. data/lib/scss_lint/linter/final_newline.rb +3 -3
  22. data/lib/scss_lint/linter/indentation.rb +20 -20
  23. data/lib/scss_lint/linter/leading_zero.rb +2 -2
  24. data/lib/scss_lint/linter/mergeable_selector.rb +3 -4
  25. data/lib/scss_lint/linter/name_format.rb +2 -1
  26. data/lib/scss_lint/linter/nesting_depth.rb +1 -1
  27. data/lib/scss_lint/linter/property_sort_order.rb +11 -13
  28. data/lib/scss_lint/linter/selector_depth.rb +9 -8
  29. data/lib/scss_lint/linter/selector_format.rb +1 -1
  30. data/lib/scss_lint/linter/shorthand.rb +1 -1
  31. data/lib/scss_lint/linter/single_line_per_selector.rb +3 -1
  32. data/lib/scss_lint/linter/space_around_operator.rb +4 -2
  33. data/lib/scss_lint/linter/space_before_brace.rb +8 -8
  34. data/lib/scss_lint/linter/space_between_parens.rb +11 -11
  35. data/lib/scss_lint/linter/string_quotes.rb +9 -10
  36. data/lib/scss_lint/linter/trailing_zero.rb +1 -1
  37. data/lib/scss_lint/linter/transition_all.rb +1 -1
  38. data/lib/scss_lint/linter/unnecessary_mantissa.rb +3 -1
  39. data/lib/scss_lint/linter/unnecessary_parent_reference.rb +9 -2
  40. data/lib/scss_lint/linter/url_quotes.rb +4 -2
  41. data/lib/scss_lint/linter/variable_for_property.rb +1 -1
  42. data/lib/scss_lint/linter/vendor_prefix.rb +3 -3
  43. data/lib/scss_lint/linter/zero_unit.rb +3 -1
  44. data/lib/scss_lint/location.rb +1 -1
  45. data/lib/scss_lint/logger.rb +149 -0
  46. data/lib/scss_lint/options.rb +5 -1
  47. data/lib/scss_lint/rake_task.rb +10 -3
  48. data/lib/scss_lint/reporter.rb +4 -2
  49. data/lib/scss_lint/reporter/default_reporter.rb +3 -3
  50. data/lib/scss_lint/version.rb +3 -1
  51. data/spec/scss_lint/cli_spec.rb +66 -14
  52. data/spec/scss_lint/config_spec.rb +25 -5
  53. data/spec/scss_lint/linter/name_format_spec.rb +10 -0
  54. data/spec/scss_lint/linter/property_sort_order_spec.rb +28 -0
  55. data/spec/scss_lint/linter/unnecessary_parent_reference_spec.rb +10 -0
  56. data/spec/scss_lint/linter/variable_for_property_spec.rb +10 -0
  57. data/spec/scss_lint/logger_spec.rb +27 -0
  58. data/spec/scss_lint/options_spec.rb +18 -0
  59. data/spec/scss_lint/plugins/linter_dir_spec.rb +1 -1
  60. data/spec/scss_lint/reporter/clean_files_reporter_spec.rb +1 -1
  61. data/spec/scss_lint/reporter/config_reporter_spec.rb +1 -1
  62. data/spec/scss_lint/reporter/default_reporter_spec.rb +2 -1
  63. data/spec/scss_lint/reporter/files_reporter_spec.rb +1 -1
  64. data/spec/scss_lint/reporter/json_reporter_spec.rb +5 -5
  65. metadata +10 -7
@@ -14,14 +14,14 @@ module SCSSLint
14
14
  yield
15
15
  end
16
16
 
17
- alias_method :visit_function, :check_node
18
- alias_method :visit_each, :check_node
19
- alias_method :visit_for, :check_node
20
- alias_method :visit_function, :check_node
21
- alias_method :visit_mixindef, :check_node
22
- alias_method :visit_mixin, :check_node
23
- alias_method :visit_rule, :check_node
24
- alias_method :visit_while, :check_node
17
+ alias visit_function check_node
18
+ alias visit_each check_node
19
+ alias visit_for check_node
20
+ alias visit_function check_node
21
+ alias visit_mixindef check_node
22
+ alias visit_mixin check_node
23
+ alias visit_rule check_node
24
+ alias visit_while check_node
25
25
 
26
26
  private
27
27
 
@@ -10,13 +10,13 @@ module SCSSLint
10
10
  yield
11
11
  end
12
12
 
13
- alias_method :visit_atroot, :check_node
14
- alias_method :visit_cssimport, :check_node
15
- alias_method :visit_function, :check_node
16
- alias_method :visit_media, :check_node
17
- alias_method :visit_mixindef, :check_node
18
- alias_method :visit_mixin, :check_node
19
- alias_method :visit_script_funcall, :check_node
13
+ alias visit_atroot check_node
14
+ alias visit_cssimport check_node
15
+ alias visit_function check_node
16
+ alias visit_media check_node
17
+ alias visit_mixindef check_node
18
+ alias visit_mixin check_node
19
+ alias visit_script_funcall check_node
20
20
 
21
21
  def feel_for_parens_and_check_node(node)
22
22
  source = feel_for_enclosing_parens(node)
@@ -24,10 +24,10 @@ module SCSSLint
24
24
  yield
25
25
  end
26
26
 
27
- alias_method :visit_script_listliteral, :feel_for_parens_and_check_node
28
- alias_method :visit_script_mapliteral, :feel_for_parens_and_check_node
29
- alias_method :visit_script_operation, :feel_for_parens_and_check_node
30
- alias_method :visit_script_string, :feel_for_parens_and_check_node
27
+ alias visit_script_listliteral feel_for_parens_and_check_node
28
+ alias visit_script_mapliteral feel_for_parens_and_check_node
29
+ alias visit_script_operation feel_for_parens_and_check_node
30
+ alias visit_script_string feel_for_parens_and_check_node
31
31
 
32
32
  private
33
33
 
@@ -12,8 +12,9 @@ module SCSSLint
12
12
  #
13
13
  # Thus we manually skip the substrings in the string interpolation and
14
14
  # visit the expressions in the interpolation itself.
15
- node.children.reject { |child| child.is_a?(Sass::Script::Tree::Literal) }
16
- .each { |child| visit(child) }
15
+ node.children
16
+ .reject { |child| child.is_a?(Sass::Script::Tree::Literal) }
17
+ .each { |child| visit(child) }
17
18
  end
18
19
 
19
20
  def visit_script_string(node)
@@ -27,7 +28,7 @@ module SCSSLint
27
28
 
28
29
  def visit_charset(node)
29
30
  # `@charset` source range includes entire declaration, so exclude that prefix
30
- source = source_from_range(node.source_range)[(CHARSET_DIRECTIVE_LENGTH)..-1]
31
+ source = source_from_range(node.source_range)[CHARSET_DIRECTIVE_LENGTH..-1]
31
32
 
32
33
  check_quotes(node, source)
33
34
  end
@@ -63,11 +64,9 @@ module SCSSLint
63
64
  def check_double_quotes(node, string)
64
65
  if config['style'] == 'single_quotes'
65
66
  add_lint(node, 'Prefer single quoted strings') if string !~ /'/
66
- else
67
- if string =~ /(?<! \\) \\"/x && string !~ /'/
68
- add_lint(node, 'Use single-quoted strings when writing double ' \
69
- 'quotes to avoid having to escape the double quotes')
70
- end
67
+ elsif string =~ /(?<! \\) \\"/x && string !~ /'/
68
+ add_lint(node, 'Use single-quoted strings when writing double ' \
69
+ 'quotes to avoid having to escape the double quotes')
71
70
  end
72
71
  end
73
72
 
@@ -79,8 +78,8 @@ module SCSSLint
79
78
  elsif string =~ /(?<! \\) \\"/x
80
79
  add_lint(node, "Don't escape double quotes in single-quoted strings")
81
80
  end
82
- else
83
- add_lint(node, 'Prefer double-quoted strings') if string !~ /"/
81
+ elsif string !~ /"/
82
+ add_lint(node, 'Prefer double-quoted strings')
84
83
  end
85
84
  end
86
85
  end
@@ -15,7 +15,7 @@ module SCSSLint
15
15
 
16
16
  def visit_script_number(node)
17
17
  return unless number =
18
- source_from_range(node.source_range)[FRACTIONAL_DIGIT_REGEX, 1]
18
+ source_from_range(node.source_range)[FRACTIONAL_DIGIT_REGEX, 1]
19
19
 
20
20
  check_for_trailing_zeros(node, number)
21
21
  end
@@ -6,7 +6,7 @@ module SCSSLint
6
6
  TRANSITION_PROPERTIES = %w[
7
7
  transition
8
8
  transition-property
9
- ]
9
+ ].freeze
10
10
 
11
11
  def visit_prop(node)
12
12
  property = node.name.first.to_s
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SCSSLint
2
4
  # Checks for the unnecessary inclusion of a zero-value mantissa in numbers.
3
5
  # (e.g. `4.0` could be written as just `4`)
@@ -34,7 +36,7 @@ module SCSSLint
34
36
  )\b
35
37
  /ix
36
38
 
37
- MESSAGE_FORMAT = '`%s` should be written without the mantissa as `%s%s`'
39
+ MESSAGE_FORMAT = '`%s` should be written without the mantissa as `%s%s`'.freeze
38
40
 
39
41
  def unnecessary_mantissa?(mantissa)
40
42
  mantissa !~ /[^0]/
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SCSSLint
2
4
  # Checks for unnecessary uses of the parent reference (&) in nested selectors.
3
5
  class Linter::UnnecessaryParentReference < Linter
4
6
  include LinterRegistry
5
7
 
6
- MESSAGE = 'Unnecessary parent selector (&)'
8
+ MESSAGE = 'Unnecessary parent selector (&)'.freeze
7
9
 
8
10
  def visit_comma_sequence(comma_sequence)
9
11
  @multiple_sequences = comma_sequence.members.size > 1
@@ -22,7 +24,7 @@ module SCSSLint
22
24
  # element {
23
25
  # & + & { ... }
24
26
  # }
25
- return if sequence.members[1..-1].any? { |ss| sequence_starts_with_parent?(ss) }
27
+ return if sequence.members[1..-1].any? { |ss| sequence_contains_parent_reference?(ss) }
26
28
 
27
29
  # Special case: allow an isolated parent to appear if it is part of a
28
30
  # comma sequence of more than one sequence, as this could be used to DRY
@@ -45,5 +47,10 @@ module SCSSLint
45
47
  first.is_a?(Sass::Selector::Parent) &&
46
48
  first.suffix.nil? # Ignore concatenated selectors, like `&-something`
47
49
  end
50
+
51
+ def sequence_contains_parent_reference?(simple_sequence)
52
+ return unless simple_sequence.is_a?(Sass::Selector::SimpleSequence)
53
+ simple_sequence.members.any? { |s| s.is_a?(Sass::Selector::Parent) }
54
+ end
48
55
  end
49
56
  end
@@ -8,8 +8,10 @@ module SCSSLint
8
8
  when Sass::Script::Tree::Literal
9
9
  check(node, node.value.value.to_s)
10
10
  when Sass::Script::Tree::ListLiteral
11
- node.value.children.select { |child| child.is_a?(Sass::Script::Tree::Literal) }
12
- .each { |child| check(node, child.value.to_s) }
11
+ node.value
12
+ .children
13
+ .select { |child| child.is_a?(Sass::Script::Tree::Literal) }
14
+ .each { |child| check(node, child.value.to_s) }
13
15
  end
14
16
 
15
17
  yield
@@ -3,7 +3,7 @@ module SCSSLint
3
3
  class Linter::VariableForProperty < Linter
4
4
  include LinterRegistry
5
5
 
6
- IGNORED_VALUES = %w[currentColor inherit transparent]
6
+ IGNORED_VALUES = %w[currentColor inherit initial transparent].freeze
7
7
 
8
8
  def visit_root(_node)
9
9
  @properties = Set.new(config['properties'])
@@ -20,9 +20,9 @@ module SCSSLint
20
20
  check_identifier(node, source_from_range(node.value.source_range))
21
21
  end
22
22
 
23
- alias_method :visit_prop, :check_node
24
- alias_method :visit_pseudo, :check_node
25
- alias_method :visit_directive, :check_node
23
+ alias visit_prop check_node
24
+ alias visit_pseudo check_node
25
+ alias visit_directive check_node
26
26
 
27
27
  private
28
28
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SCSSLint
2
4
  # Checks for unnecessary units on zero values.
3
5
  class Linter::ZeroUnit < Linter
@@ -28,7 +30,7 @@ module SCSSLint
28
30
  \b
29
31
  /ix
30
32
 
31
- MESSAGE_FORMAT = '`%s` should be written without units as `0`'
33
+ MESSAGE_FORMAT = '`%s` should be written without units as `0`'.freeze
32
34
 
33
35
  LENGTH_UNITS = %w[em ex ch rem vw vh vmin vmax cm mm in pt pc px].to_set
34
36
 
@@ -24,7 +24,7 @@ module SCSSLint
24
24
  end
25
25
  end
26
26
 
27
- alias_method :eql?, :==
27
+ alias eql? ==
28
28
 
29
29
  def <=>(other)
30
30
  [:line, :column, :length].each do |attr|
@@ -0,0 +1,149 @@
1
+ module SCSSLint
2
+ # Encapsulates all communication to an output source.
3
+ class Logger
4
+ # Whether colored output via ANSI escape sequences is enabled.
5
+ # @return [true,false]
6
+ attr_accessor :color_enabled
7
+
8
+ # Creates a logger which outputs nothing.
9
+ # @return [SCSSLint::Logger]
10
+ def self.silent
11
+ new(File.open('/dev/null', 'w'))
12
+ end
13
+
14
+ # Creates a new {SCSSLint::Logger} instance.
15
+ #
16
+ # @param out [IO] the output destination.
17
+ def initialize(out)
18
+ @out = out
19
+ end
20
+
21
+ # Print the specified output.
22
+ #
23
+ # @param output [String] the output to send
24
+ # @param newline [true,false] whether to append a newline
25
+ def log(output, newline = true)
26
+ @out.print(output)
27
+ @out.print("\n") if newline
28
+ end
29
+
30
+ # Print the specified output in a color indicative of error.
31
+ # If output destination is not a TTY, behaves the same as {#log}.
32
+ #
33
+ # @param output [String] the output to send
34
+ # @param newline [true,false] whether to append a newline
35
+ def error(output, newline = true)
36
+ log(red(output), newline)
37
+ end
38
+
39
+ # Print the specified output in a bold face and color indicative of error.
40
+ # If output destination is not a TTY, behaves the same as {#log}.
41
+ #
42
+ # @param output [String] the output to send
43
+ # @param newline [true,false] whether to append a newline
44
+ def bold_error(output, newline = true)
45
+ log(bold_red(output), newline)
46
+ end
47
+
48
+ # Print the specified output in a color indicative of success.
49
+ # If output destination is not a TTY, behaves the same as {#log}.
50
+ #
51
+ # @param output [String] the output to send
52
+ # @param newline [true,false] whether to append a newline
53
+ def success(output, newline = true)
54
+ log(green(output), newline)
55
+ end
56
+
57
+ # Print the specified output in a color indicative of a warning.
58
+ # If output destination is not a TTY, behaves the same as {#log}.
59
+ #
60
+ # @param output [String] the output to send
61
+ # @param newline [true,false] whether to append a newline
62
+ def warning(output, newline = true)
63
+ log(yellow(output), newline)
64
+ end
65
+
66
+ # Print the specified output in a color indicating information.
67
+ # If output destination is not a TTY, behaves the same as {#log}.
68
+ #
69
+ # @param output [String] the output to send
70
+ # @param newline [true,false] whether to append a newline
71
+ def info(output, newline = true)
72
+ log(cyan(output), newline)
73
+ end
74
+
75
+ # Print a blank line.
76
+ def newline
77
+ log('')
78
+ end
79
+
80
+ # Mark the specified output in bold face.
81
+ # If output destination is not a TTY, this is a noop.
82
+ #
83
+ # @param output [String] the output to format
84
+ def bold(output)
85
+ color('1', output)
86
+ end
87
+
88
+ # Mark the specified output in bold red.
89
+ # If output destination is not a TTY, this is a noop.
90
+ #
91
+ # @param output [String] the output to format
92
+ def bold_red(output)
93
+ color('1;31', output)
94
+ end
95
+
96
+ # Mark the specified output in red.
97
+ # If output destination is not a TTY, this is a noop.
98
+ #
99
+ # @param output [String] the output to format
100
+ def red(output)
101
+ color(31, output)
102
+ end
103
+
104
+ # Mark the specified output in green.
105
+ # If output destination is not a TTY, this is a noop.
106
+ #
107
+ # @param output [String] the output to format
108
+ def green(output)
109
+ color(32, output)
110
+ end
111
+
112
+ # Mark the specified output in yellow.
113
+ # If output destination is not a TTY, this is a noop.
114
+ #
115
+ # @param output [String] the output to format
116
+ def yellow(output)
117
+ color(33, output)
118
+ end
119
+
120
+ # Mark the specified output in magenta.
121
+ # If output destination is not a TTY, this is a noop.
122
+ #
123
+ # @param output [String] the output to format
124
+ def magenta(output)
125
+ color(35, output)
126
+ end
127
+
128
+ # Mark the specified output in cyan.
129
+ # If output destination is not a TTY, this is a noop.
130
+ #
131
+ # @param output [String] the output to format
132
+ def cyan(output)
133
+ color(36, output)
134
+ end
135
+
136
+ # Whether this logger is outputting to a TTY.
137
+ #
138
+ # @return [true,false]
139
+ def tty?
140
+ @out.respond_to?(:tty?) && @out.tty?
141
+ end
142
+
143
+ private
144
+
145
+ def color(code, output)
146
+ color_enabled ? "\033[#{code}m#{output}\033[0m" : output
147
+ end
148
+ end
149
+ end
@@ -3,7 +3,7 @@ require 'optparse'
3
3
  module SCSSLint
4
4
  # Handles option parsing for the command line application.
5
5
  class Options
6
- DEFAULT_REPORTER = ['Default', :stdout]
6
+ DEFAULT_REPORTER = ['Default', :stdout].freeze
7
7
 
8
8
  # Parses command line options into an options hash.
9
9
  #
@@ -102,6 +102,10 @@ module SCSSLint
102
102
  @options[:show_linters] = true
103
103
  end
104
104
 
105
+ parser.on('--[no-]color', 'Force output to be colorized') do |color|
106
+ @options[:color] = color
107
+ end
108
+
105
109
  parser.on_tail('-h', '--help', 'Display help documentation') do
106
110
  @options[:help] = parser.help
107
111
  end
@@ -42,6 +42,11 @@ module SCSSLint
42
42
  # @return [Array<String>]
43
43
  attr_accessor :files
44
44
 
45
+ # Whether output from SCSS-lint should not be displayed to the standard out
46
+ # stream.
47
+ # @return [true,false]
48
+ attr_accessor :quiet
49
+
45
50
  # Create the task so it is accessible via +Rake::Task['scss_lint']+.
46
51
  def initialize(name = :scss_lint)
47
52
  @name = name
@@ -68,10 +73,12 @@ module SCSSLint
68
73
  end
69
74
  end
70
75
 
71
- def run_cli(task_args)
76
+ def run_cli(task_args) # rubocop:disable AbcSize
72
77
  cli_args = ['--config', config] if config
73
78
 
74
- result = SCSSLint::CLI.new.run(Array(cli_args) + Array(args) + files_to_lint(task_args))
79
+ logger = quiet ? SCSSLint::Logger.silent : SCSSLint::Logger.new(STDOUT)
80
+ run_args = Array(cli_args) + Array(args) + files_to_lint(task_args)
81
+ result = SCSSLint::CLI.new(logger).run(run_args)
75
82
 
76
83
  message =
77
84
  case result
@@ -83,7 +90,7 @@ module SCSSLint
83
90
  'scss-lint failed with an error'
84
91
  end
85
92
 
86
- puts message
93
+ logger.log message
87
94
  exit result unless result == 0
88
95
  end
89
96
 
@@ -1,7 +1,7 @@
1
1
  module SCSSLint
2
2
  # Responsible for displaying lints to the user in some format.
3
3
  class Reporter
4
- attr_reader :lints, :files
4
+ attr_reader :lints, :files, :log
5
5
 
6
6
  def self.descendants
7
7
  ObjectSpace.each_object(Class).select { |klass| klass < self }
@@ -9,9 +9,11 @@ module SCSSLint
9
9
 
10
10
  # @param lints [List<Lint>] a list of Lints sorted by file and line number
11
11
  # @param files [List<String>] a list of the files that were linted
12
- def initialize(lints, files)
12
+ # @param logger [SCSSLint::Logger]
13
+ def initialize(lints, files, logger)
13
14
  @lints = lints
14
15
  @files = files
16
+ @log = logger
15
17
  end
16
18
 
17
19
  def report_lints