scss-lint 0.18.0 → 0.19.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4ba79ac6014b4db405c0954003c2ec1ac2753419
4
- data.tar.gz: 3a6d1cf5b8453c4766d6f95d9ff1df3e8267515c
3
+ metadata.gz: 851fc6dcdb6402f514538367d2a2e10afa223a23
4
+ data.tar.gz: f9c630b4c98aaf277a4277abb003144ba65e5bd8
5
5
  SHA512:
6
- metadata.gz: 0971a5a157de5fd32506a46d2935dfa07a476d648580bdb4e72edbe435fc47754903a2356dde7c8ffa2f8825d11a631e07fa50bc954a99bafc6ef7ef2187d61e
7
- data.tar.gz: 3b463780c5fdcef0d2e06fe17bd2dd1739ebbc7bf8c0f473acc0e18023633e69e7de37762c5efc1819a6c62f8372569c56612ba255dedc88c3cdc1ec0093c873
6
+ metadata.gz: 9016d101642210006cda461af3c990c6c0bb04e254ff49dd50e19ee3a04dbc87db450b8d687ae47972015bdc564cb3d2bd93aea9131065a79665777e016eff45
7
+ data.tar.gz: 3c9cffd85b8d147e1bcf4c7617a2cda7ea8cf81e50ff33a059b1ad0ca0634835c200802ade936fefdf6c89a283d37bc35fa41a719078fdbba4cb4147dbf15e13
@@ -1,6 +1,6 @@
1
1
  require 'scss_lint/constants'
2
- require 'scss_lint/cli'
3
2
  require 'scss_lint/config'
3
+ require 'scss_lint/cli'
4
4
  require 'scss_lint/engine'
5
5
  require 'scss_lint/lint'
6
6
  require 'scss_lint/linter_registry'
@@ -9,18 +9,19 @@ module SCSSLint
9
9
 
10
10
  # Subset of semantic exit codes conforming to `sysexits` documentation.
11
11
  EXIT_CODES = {
12
- ok: 0,
13
- usage: 64, # Command line usage error
14
- data: 65, # User input was incorrect (i.e. contains lints)
15
- no_input: 66, # Input file did not exist or was not readable
16
- software: 70, # Internal software error
17
- config: 78, # Configuration error
12
+ ok: 0,
13
+ usage: 64, # Command line usage error
14
+ data: 65, # User input was incorrect (i.e. contains lints)
15
+ no_input: 66, # Input file did not exist or was not readable
16
+ software: 70, # Internal software error
17
+ config: 78, # Configuration error
18
18
  }
19
19
 
20
+ # @param args [Array]
20
21
  def initialize(args = [])
21
- @args = args
22
+ @args = args
22
23
  @options = {}
23
- @config = Config.default
24
+ @config = Config.default
24
25
  end
25
26
 
26
27
  def parse_arguments
@@ -35,12 +36,13 @@ module SCSSLint
35
36
 
36
37
  begin
37
38
  setup_configuration
38
- rescue NoSuchLinter => ex
39
+ rescue InvalidConfiguration, NoSuchLinter => ex
39
40
  puts ex.message
40
41
  halt :config
41
42
  end
42
43
  end
43
44
 
45
+ # @return [OptionParser]
44
46
  def options_parser
45
47
  @options_parser ||= OptionParser.new do |opts|
46
48
  opts.banner = "Usage: #{opts.program_name} [options] [scss-files]"
@@ -90,6 +92,9 @@ module SCSSLint
90
92
  runner.run(files_to_lint)
91
93
  report_lints(runner.lints)
92
94
  halt :data if runner.lints.any?
95
+ rescue InvalidConfiguration => ex
96
+ puts ex
97
+ halt :config
93
98
  rescue NoFilesError, Errno::ENOENT => ex
94
99
  puts ex.message
95
100
  halt :no_input
@@ -114,6 +119,8 @@ module SCSSLint
114
119
  merge_command_line_flags_with_config(@config)
115
120
  end
116
121
 
122
+ # @param config [Config]
123
+ # @return [Config]
117
124
  def merge_command_line_flags_with_config(config)
118
125
  if @options[:excluded_files]
119
126
  @options[:excluded_files].each do |file|
@@ -150,6 +157,7 @@ module SCSSLint
150
157
  end
151
158
  end
152
159
 
160
+ # @param list [Array]
153
161
  def extract_files_from(list)
154
162
  files = []
155
163
  list.each do |file|
@@ -161,12 +169,15 @@ module SCSSLint
161
169
  end
162
170
 
163
171
  VALID_EXTENSIONS = %w[.css .scss]
172
+ # @param file [String]
173
+ # @return [Boolean]
164
174
  def scssish_file?(file)
165
175
  return false unless FileTest.file?(file)
166
176
 
167
177
  VALID_EXTENSIONS.include?(File.extname(file))
168
178
  end
169
179
 
180
+ # @param lints [Array<Lint>]
170
181
  def report_lints(lints)
171
182
  sorted_lints = lints.sort_by { |l| [l.filename, l.line] }
172
183
  reporter = @options.fetch(:reporter, Reporter::DefaultReporter)
@@ -175,6 +186,7 @@ module SCSSLint
175
186
  print output if output
176
187
  end
177
188
 
189
+ # @param format [String]
178
190
  def set_output_format(format)
179
191
  @options[:reporter] = SCSSLint::Reporter.const_get(format + 'Reporter')
180
192
  rescue NameError
@@ -196,18 +208,23 @@ module SCSSLint
196
208
  halt
197
209
  end
198
210
 
211
+ # @param help_message [String]
212
+ # @param err [Exception]
199
213
  def print_help(help_message, err = nil)
200
214
  puts err, '' if err
201
215
  puts help_message
202
216
  halt(err ? :usage : :ok)
203
217
  end
204
218
 
219
+ # @param program_name [String]
220
+ # @param version [String]
205
221
  def print_version(program_name, version)
206
222
  puts "#{program_name} #{version}"
207
223
  halt
208
224
  end
209
225
 
210
226
  # Used for ease-of testing
227
+ # @param exit_status [Symbol]
211
228
  def halt(exit_status = :ok)
212
229
  exit(EXIT_CODES[exit_status])
213
230
  end
@@ -2,6 +2,9 @@ require 'pathname'
2
2
  require 'yaml'
3
3
 
4
4
  module SCSSLint
5
+ # Raised when the configuration file is invalid for some reason.
6
+ class InvalidConfiguration < StandardError; end
7
+
5
8
  # Loads and manages application configuration.
6
9
  class Config
7
10
  FILE_NAME = '.scss-lint.yml'
@@ -61,12 +64,16 @@ module SCSSLint
61
64
  def load_options_hash_from_file(file)
62
65
  file_contents = load_file_contents(file)
63
66
 
64
- options =
65
- if yaml = YAML.load(file_contents)
66
- yaml.to_hash
67
- else
68
- {}
69
- end
67
+ begin
68
+ options =
69
+ if yaml = YAML.load(file_contents)
70
+ yaml.to_hash
71
+ else
72
+ {}
73
+ end
74
+ rescue => ex
75
+ raise InvalidConfiguration, "Invalid configuration: #{ex.message}"
76
+ end
70
77
 
71
78
  options = convert_single_options_to_arrays(options)
72
79
  options = extend_inherited_configs(options, file)
@@ -3,13 +3,18 @@ module SCSSLint
3
3
  class Lint
4
4
  attr_reader :filename, :line, :description, :severity
5
5
 
6
+ # @param filename [String]
7
+ # @param line [Integer]
8
+ # @param description [String]
9
+ # @param severity [Symbol]
6
10
  def initialize(filename, line, description, severity = :warning)
7
- @filename = filename
8
- @line = line
11
+ @filename = filename
12
+ @line = line
9
13
  @description = description
10
- @severity = severity
14
+ @severity = severity
11
15
  end
12
16
 
17
+ # @return [Boolean]
13
18
  def error?
14
19
  severity == :error
15
20
  end
@@ -10,6 +10,8 @@ module SCSSLint
10
10
  @lints = []
11
11
  end
12
12
 
13
+ # @param engine [Engine]
14
+ # @param config [Config]
13
15
  def run(engine, config)
14
16
  @config = config
15
17
  @engine = engine
@@ -17,11 +19,15 @@ module SCSSLint
17
19
  end
18
20
 
19
21
  # Define if you want a default message for your linter
22
+ # @return [String, nil]
20
23
  def description
21
24
  nil
22
25
  end
23
26
 
24
27
  # Helper for creating lint from a parse tree node
28
+ #
29
+ # @param node_or_line [Sass::Script::Tree::Node, Sass::Engine::Line]
30
+ # @param message [String, nil]
25
31
  def add_lint(node_or_line, message = nil)
26
32
  line = node_or_line.respond_to?(:line) ? node_or_line.line : node_or_line
27
33
 
@@ -30,9 +36,11 @@ module SCSSLint
30
36
  message || description)
31
37
  end
32
38
 
33
- # Returns the character at the given [Sass::Source::Position]
39
+ # @param source_position [Sass::Source::Position]
40
+ # @param offset [Integer]
41
+ # @return [String] the character at the given [Sass::Source::Position]
34
42
  def character_at(source_position, offset = 0)
35
- actual_line = source_position.line - 1
43
+ actual_line = source_position.line - 1
36
44
  actual_offset = source_position.offset + offset - 1
37
45
 
38
46
  # Return a newline if offset points at the very end of the line
@@ -42,11 +50,19 @@ module SCSSLint
42
50
  end
43
51
 
44
52
  # Extracts the original source code given a range.
53
+ #
54
+ # @param source_range [Sass::Source::Range]
55
+ # @return [String] the original source code
45
56
  def source_from_range(source_range)
46
57
  current_line = source_range.start_pos.line - 1
47
- last_line = source_range.end_pos.line - 1
58
+ last_line = source_range.end_pos.line - 1
59
+ start_pos = source_range.start_pos.offset - 1
48
60
 
49
- source = engine.lines[current_line][(source_range.start_pos.offset - 1)..-1]
61
+ if current_line == last_line
62
+ source = engine.lines[current_line][start_pos..(source_range.end_pos.offset - 1)]
63
+ else
64
+ source = engine.lines[current_line][start_pos..-1]
65
+ end
50
66
 
51
67
  current_line += 1
52
68
  while current_line < last_line
@@ -64,19 +80,10 @@ module SCSSLint
64
80
  source
65
81
  end
66
82
 
67
- # Monkey-patched implementation that adds support for traversing
68
- # Sass::Script::Nodes (original implementation only supports
69
- # Sass::Tree::Nodes).
70
- def self.node_name(node)
71
- case node
72
- when Sass::Script::Tree::Node, Sass::Script::Value::Base
73
- "script_#{node.class.name.gsub(/.*::(.*?)$/, '\\1').downcase}"
74
- else
75
- super
76
- end
77
- end
78
-
79
83
  # Modified so we can also visit selectors in linters
84
+ #
85
+ # @param node [Sass::Tree::Node, Sass::Script::Tree::Node,
86
+ # Sass::Script::Value::Base]
80
87
  def visit(node)
81
88
  # Visit the selector of a rule if parsed rules are available
82
89
  if node.is_a?(Sass::Tree::RuleNode) && node.parsed_rules
@@ -87,6 +94,9 @@ module SCSSLint
87
94
  end
88
95
 
89
96
  # Redefine so we can set the `node_parent` of each node
97
+ #
98
+ # @param parent [Sass::Tree::Node, Sass::Script::Tree::Node,
99
+ # Sass::Script::Value::Base]
90
100
  def visit_children(parent)
91
101
  parent.children.each do |child|
92
102
  child.node_parent = parent
@@ -13,7 +13,7 @@ module SCSSLint
13
13
  sortable_prop_names = sortable_props.map { |child| child.name.join }
14
14
 
15
15
  sorted_prop_names = sortable_prop_names.map do |name|
16
- /^(?<vendor>-\w+-)?(?<property>.+)/ =~ name
16
+ /^(?<vendor>-\w+(-osx)?-)?(?<property>.+)/ =~ name
17
17
  { name: name, vendor: vendor, property: property }
18
18
  end.sort { |a, b| compare_properties(a, b) }
19
19
  .map { |fields| fields[:name] }
@@ -66,20 +66,33 @@ module SCSSLint
66
66
  def check_commas_after_args(args, arg_type)
67
67
  # For each arg except the last, check the character following the comma
68
68
  args[0..-2].each do |arg|
69
- # Find the first comma following the arg
70
- offset = 1
71
- offset += 1 while character_at(arg.source_range.end_pos, offset - 1) != ','
69
+ offset = 0
70
+
71
+ # Find the comma following this argument.
72
+ # The Sass parser is unpredictable in where it marks the end of the
73
+ # source range. Thus we need to start at the indicated range, and check
74
+ # left and right of that range, gradually moving further outward until
75
+ # we find the comma.
76
+ if character_at(arg.source_range.end_pos, offset) != ','
77
+ loop do
78
+ offset += 1
79
+ break if character_at(arg.source_range.end_pos, offset) == ','
80
+ offset = -offset
81
+ break if character_at(arg.source_range.end_pos, offset) == ','
82
+ offset = -offset
83
+ end
84
+ end
72
85
 
73
86
  # Check for space or newline after arg (we allow arguments to be split
74
87
  # up over multiple lines).
75
88
  spaces = 0
76
- while character_at(arg.source_range.end_pos, offset) =~ / |\n/
89
+ while character_at(arg.source_range.end_pos, offset + 1) =~ / |\n/
77
90
  spaces += 1
78
91
  offset += 1
79
92
  end
80
93
 
81
94
  if spaces != EXPECTED_SPACES_AFTER_COMMA
82
- add_lint arg, 'Commas in %s should be followed by a single space' % arg_type
95
+ add_lint arg, "Commas in #{arg_type} should be followed by a single space"
83
96
  end
84
97
  end
85
98
  end
@@ -8,12 +8,12 @@ module SCSSLint
8
8
  end
9
9
 
10
10
  def visit_import(node)
11
- # @import source range conveniently includes only the quoted string
11
+ # `@import` source range conveniently includes only the quoted string
12
12
  check_quotes(node, source_from_range(node.source_range))
13
13
  end
14
14
 
15
15
  def visit_charset(node)
16
- # @charset source range includes entire declaration, so exclude '@charset' prefix
16
+ # `@charset` source range includes entire declaration, so exclude that prefix
17
17
  source = source_from_range(node.source_range)[('@charset'.length)..-1]
18
18
 
19
19
  check_quotes(node, source)
@@ -10,7 +10,7 @@ module SCSSLint
10
10
  end
11
11
 
12
12
  def visit_script_number(node)
13
- if node.value == 0 && !node.unitless?
13
+ if node.value == 0 && zero_with_units?(source_from_range(node.source_range))
14
14
  add_lint(node, MESSAGE_FORMAT % node.original_string)
15
15
  end
16
16
  end
@@ -18,5 +18,9 @@ module SCSSLint
18
18
  private
19
19
 
20
20
  MESSAGE_FORMAT = '`%s` should be written without units as `0`'
21
+
22
+ def zero_with_units?(string)
23
+ string =~ /^0[a-z]+/
24
+ end
21
25
  end
22
26
  end
@@ -7,12 +7,14 @@ module SCSSLint
7
7
  class Runner
8
8
  attr_reader :lints
9
9
 
10
+ # @param config [Config]
10
11
  def initialize(config)
11
- @config = config
12
- @lints = []
12
+ @config = config
13
+ @lints = []
13
14
  @linters = LinterRegistry.linters.map(&:new)
14
15
  end
15
16
 
17
+ # @param files [Array]
16
18
  def run(files)
17
19
  raise NoFilesError, 'No SCSS files specified' if files.empty?
18
20
 
@@ -27,6 +29,7 @@ module SCSSLint
27
29
 
28
30
  private
29
31
 
32
+ # @param file [String]
30
33
  def find_lints(file)
31
34
  engine = Engine.new(file)
32
35
  config = @config.preferred ? @config : Config.for_file(file)
@@ -1,7 +1,7 @@
1
- module Sass::Script
2
- # Ignore documentation lints as these aren't original implementations.
3
- # rubocop:disable Documentation
1
+ # Ignore documentation lints as these aren't original implementations.
2
+ # rubocop:disable Documentation
4
3
 
4
+ module Sass::Script
5
5
  # Redefine some of the lexer helpers in order to store the original string
6
6
  # with the created object so that the original string can be inspected rather
7
7
  # than a typically normalized version.
@@ -43,6 +43,38 @@ module Sass::Script
43
43
  end
44
44
  end
45
45
 
46
+ # Since the Sass library is already loaded at this point.
47
+ # Define the `node_name` and `visit_method` class methods for each Sass Script
48
+ # parse tree node type so that our custom visitor can seamless traverse the
49
+ # tree.
50
+ #
51
+ # This would be easier if we could just define an `inherited` callback, but
52
+ # that won't work since the Sass library will have already been loaded before
53
+ # this code gets loaded, so the `inherited` callback won't be fired.
54
+ #
55
+ # Thus we are left to manually define the methods for each type explicitly.
56
+ {
57
+ 'Value' => %w[ArgList Bool Color List Map Null Number String],
58
+ 'Tree' => %w[Funcall Interpolation ListLiteral Literal MapLiteral
59
+ Operation StringInterpolation UnaryOperation Variable],
60
+ }.each do |namespace, types|
61
+ types.each do |type|
62
+ node_name = type.downcase
63
+
64
+ eval <<-DECL
65
+ class #{namespace}::#{type}
66
+ def self.node_name
67
+ :script_#{node_name}
68
+ end
69
+
70
+ def self.visit_method
71
+ :visit_script_#{node_name}
72
+ end
73
+ end
74
+ DECL
75
+ end
76
+ end
77
+
46
78
  class Value::Base
47
79
  attr_accessor :node_parent
48
80
 
@@ -1,4 +1,4 @@
1
1
  # Defines the gem version.
2
2
  module SCSSLint
3
- VERSION = '0.18.0'
3
+ VERSION = '0.19.0'
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scss-lint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Causes Engineering
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-03-07 00:00:00.000000000 Z
12
+ date: 2014-03-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: colorize
@@ -29,16 +29,16 @@ dependencies:
29
29
  name: sass
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - '='
32
+ - - ~>
33
33
  - !ruby/object:Gem::Version
34
- version: 3.3.0.rc.1
34
+ version: 3.3.0
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - '='
39
+ - - ~>
40
40
  - !ruby/object:Gem::Version
41
- version: 3.3.0.rc.1
41
+ version: 3.3.0
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: nokogiri
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -67,7 +67,7 @@ dependencies:
67
67
  - - '='
68
68
  - !ruby/object:Gem::Version
69
69
  version: 2.14.1
70
- description: Configurable tool to help write clean and consistent SCSS
70
+ description: Configurable tool for writing clean and consistent SCSS
71
71
  email:
72
72
  - eng@causes.com
73
73
  - shane@causes.com