scss-lint 0.18.0 → 0.19.0

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