oreorenasass 3.4.4 → 3.4.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +50 -70
  4. data/Rakefile +5 -26
  5. data/VERSION +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/bin/sass +1 -1
  8. data/bin/scss +1 -1
  9. data/lib/sass.rb +12 -19
  10. data/lib/sass/cache_stores/base.rb +2 -2
  11. data/lib/sass/cache_stores/chain.rb +1 -2
  12. data/lib/sass/cache_stores/filesystem.rb +5 -1
  13. data/lib/sass/cache_stores/memory.rb +1 -1
  14. data/lib/sass/cache_stores/null.rb +2 -2
  15. data/lib/sass/callbacks.rb +0 -1
  16. data/lib/sass/css.rb +13 -11
  17. data/lib/sass/engine.rb +173 -424
  18. data/lib/sass/environment.rb +58 -148
  19. data/lib/sass/error.rb +14 -11
  20. data/lib/sass/exec.rb +703 -5
  21. data/lib/sass/importers/base.rb +6 -49
  22. data/lib/sass/importers/filesystem.rb +19 -44
  23. data/lib/sass/logger.rb +4 -1
  24. data/lib/sass/logger/base.rb +4 -2
  25. data/lib/sass/logger/log_level.rb +7 -3
  26. data/lib/sass/media.rb +23 -20
  27. data/lib/sass/plugin.rb +7 -7
  28. data/lib/sass/plugin/compiler.rb +145 -304
  29. data/lib/sass/plugin/configuration.rb +23 -18
  30. data/lib/sass/plugin/merb.rb +1 -1
  31. data/lib/sass/plugin/staleness_checker.rb +3 -3
  32. data/lib/sass/repl.rb +3 -3
  33. data/lib/sass/script.rb +8 -35
  34. data/lib/sass/script/{value/arg_list.rb → arg_list.rb} +25 -9
  35. data/lib/sass/script/bool.rb +18 -0
  36. data/lib/sass/script/color.rb +606 -0
  37. data/lib/sass/script/css_lexer.rb +4 -8
  38. data/lib/sass/script/css_parser.rb +2 -5
  39. data/lib/sass/script/funcall.rb +245 -0
  40. data/lib/sass/script/functions.rb +408 -1491
  41. data/lib/sass/script/interpolation.rb +79 -0
  42. data/lib/sass/script/lexer.rb +68 -172
  43. data/lib/sass/script/list.rb +85 -0
  44. data/lib/sass/script/literal.rb +221 -0
  45. data/lib/sass/script/{tree/node.rb → node.rb} +12 -22
  46. data/lib/sass/script/{value/null.rb → null.rb} +7 -14
  47. data/lib/sass/script/{value/number.rb → number.rb} +75 -152
  48. data/lib/sass/script/{tree/operation.rb → operation.rb} +24 -17
  49. data/lib/sass/script/parser.rb +110 -245
  50. data/lib/sass/script/string.rb +51 -0
  51. data/lib/sass/script/{tree/string_interpolation.rb → string_interpolation.rb} +4 -5
  52. data/lib/sass/script/{tree/unary_operation.rb → unary_operation.rb} +6 -6
  53. data/lib/sass/script/variable.rb +58 -0
  54. data/lib/sass/scss/css_parser.rb +3 -9
  55. data/lib/sass/scss/parser.rb +421 -450
  56. data/lib/sass/scss/rx.rb +11 -19
  57. data/lib/sass/scss/static_parser.rb +7 -321
  58. data/lib/sass/selector.rb +194 -68
  59. data/lib/sass/selector/abstract_sequence.rb +14 -29
  60. data/lib/sass/selector/comma_sequence.rb +25 -108
  61. data/lib/sass/selector/sequence.rb +66 -159
  62. data/lib/sass/selector/simple.rb +25 -23
  63. data/lib/sass/selector/simple_sequence.rb +63 -173
  64. data/lib/sass/shared.rb +1 -1
  65. data/lib/sass/supports.rb +15 -13
  66. data/lib/sass/tree/charset_node.rb +1 -1
  67. data/lib/sass/tree/comment_node.rb +3 -3
  68. data/lib/sass/tree/css_import_node.rb +11 -11
  69. data/lib/sass/tree/debug_node.rb +2 -2
  70. data/lib/sass/tree/directive_node.rb +4 -21
  71. data/lib/sass/tree/each_node.rb +8 -8
  72. data/lib/sass/tree/extend_node.rb +7 -14
  73. data/lib/sass/tree/for_node.rb +4 -4
  74. data/lib/sass/tree/function_node.rb +4 -9
  75. data/lib/sass/tree/if_node.rb +1 -1
  76. data/lib/sass/tree/import_node.rb +5 -4
  77. data/lib/sass/tree/media_node.rb +14 -4
  78. data/lib/sass/tree/mixin_def_node.rb +4 -4
  79. data/lib/sass/tree/mixin_node.rb +8 -21
  80. data/lib/sass/tree/node.rb +12 -54
  81. data/lib/sass/tree/prop_node.rb +20 -39
  82. data/lib/sass/tree/return_node.rb +2 -3
  83. data/lib/sass/tree/root_node.rb +3 -19
  84. data/lib/sass/tree/rule_node.rb +22 -35
  85. data/lib/sass/tree/supports_node.rb +13 -0
  86. data/lib/sass/tree/trace_node.rb +1 -2
  87. data/lib/sass/tree/variable_node.rb +3 -9
  88. data/lib/sass/tree/visitors/base.rb +8 -5
  89. data/lib/sass/tree/visitors/check_nesting.rb +19 -49
  90. data/lib/sass/tree/visitors/convert.rb +56 -74
  91. data/lib/sass/tree/visitors/cssize.rb +74 -202
  92. data/lib/sass/tree/visitors/deep_copy.rb +5 -10
  93. data/lib/sass/tree/visitors/extend.rb +7 -7
  94. data/lib/sass/tree/visitors/perform.rb +185 -278
  95. data/lib/sass/tree/visitors/set_options.rb +6 -20
  96. data/lib/sass/tree/visitors/to_css.rb +81 -234
  97. data/lib/sass/tree/warn_node.rb +2 -2
  98. data/lib/sass/tree/while_node.rb +2 -2
  99. data/lib/sass/util.rb +152 -522
  100. data/lib/sass/util/multibyte_string_scanner.rb +0 -2
  101. data/lib/sass/util/subset_map.rb +3 -4
  102. data/lib/sass/util/test.rb +1 -0
  103. data/lib/sass/version.rb +22 -20
  104. data/test/Gemfile +3 -0
  105. data/test/Gemfile.lock +10 -0
  106. data/test/sass/cache_test.rb +20 -62
  107. data/test/sass/callbacks_test.rb +1 -1
  108. data/test/sass/conversion_test.rb +2 -296
  109. data/test/sass/css2sass_test.rb +4 -23
  110. data/test/sass/engine_test.rb +354 -411
  111. data/test/sass/exec_test.rb +2 -2
  112. data/test/sass/extend_test.rb +145 -324
  113. data/test/sass/functions_test.rb +86 -873
  114. data/test/sass/importer_test.rb +21 -241
  115. data/test/sass/logger_test.rb +1 -1
  116. data/test/sass/more_results/more_import.css +1 -1
  117. data/test/sass/plugin_test.rb +26 -16
  118. data/test/sass/results/compact.css +1 -1
  119. data/test/sass/results/complex.css +4 -4
  120. data/test/sass/results/expanded.css +1 -1
  121. data/test/sass/results/import.css +1 -1
  122. data/test/sass/results/import_charset_ibm866.css +2 -2
  123. data/test/sass/results/mixins.css +17 -17
  124. data/test/sass/results/nested.css +1 -1
  125. data/test/sass/results/parent_ref.css +2 -2
  126. data/test/sass/results/script.css +3 -3
  127. data/test/sass/results/scss_import.css +1 -1
  128. data/test/sass/script_conversion_test.rb +7 -36
  129. data/test/sass/script_test.rb +53 -485
  130. data/test/sass/scss/css_test.rb +28 -143
  131. data/test/sass/scss/rx_test.rb +4 -4
  132. data/test/sass/scss/scss_test.rb +325 -2119
  133. data/test/sass/templates/scss_import.scss +1 -2
  134. data/test/sass/test_helper.rb +1 -1
  135. data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
  136. data/test/sass/util/subset_map_test.rb +2 -2
  137. data/test/sass/util_test.rb +1 -86
  138. data/test/test_helper.rb +8 -37
  139. metadata +19 -66
  140. data/lib/sass/exec/base.rb +0 -187
  141. data/lib/sass/exec/sass_convert.rb +0 -264
  142. data/lib/sass/exec/sass_scss.rb +0 -424
  143. data/lib/sass/features.rb +0 -47
  144. data/lib/sass/script/tree.rb +0 -16
  145. data/lib/sass/script/tree/funcall.rb +0 -306
  146. data/lib/sass/script/tree/interpolation.rb +0 -118
  147. data/lib/sass/script/tree/list_literal.rb +0 -77
  148. data/lib/sass/script/tree/literal.rb +0 -45
  149. data/lib/sass/script/tree/map_literal.rb +0 -64
  150. data/lib/sass/script/tree/selector.rb +0 -26
  151. data/lib/sass/script/tree/variable.rb +0 -57
  152. data/lib/sass/script/value.rb +0 -11
  153. data/lib/sass/script/value/base.rb +0 -240
  154. data/lib/sass/script/value/bool.rb +0 -35
  155. data/lib/sass/script/value/color.rb +0 -680
  156. data/lib/sass/script/value/helpers.rb +0 -262
  157. data/lib/sass/script/value/list.rb +0 -113
  158. data/lib/sass/script/value/map.rb +0 -70
  159. data/lib/sass/script/value/string.rb +0 -97
  160. data/lib/sass/selector/pseudo.rb +0 -256
  161. data/lib/sass/source/map.rb +0 -210
  162. data/lib/sass/source/position.rb +0 -39
  163. data/lib/sass/source/range.rb +0 -41
  164. data/lib/sass/stack.rb +0 -120
  165. data/lib/sass/tree/at_root_node.rb +0 -83
  166. data/lib/sass/tree/error_node.rb +0 -18
  167. data/lib/sass/tree/keyframe_rule_node.rb +0 -15
  168. data/lib/sass/util/cross_platform_random.rb +0 -19
  169. data/lib/sass/util/normalized_map.rb +0 -130
  170. data/lib/sass/util/ordered_hash.rb +0 -192
  171. data/test/sass/compiler_test.rb +0 -232
  172. data/test/sass/encoding_test.rb +0 -219
  173. data/test/sass/source_map_test.rb +0 -977
  174. data/test/sass/superselector_test.rb +0 -191
  175. data/test/sass/util/normalized_map_test.rb +0 -51
  176. data/test/sass/value_helpers_test.rb +0 -179
@@ -0,0 +1,51 @@
1
+ require 'sass/script/literal'
2
+
3
+ module Sass::Script
4
+ # A SassScript object representing a CSS string *or* a CSS identifier.
5
+ class String < Literal
6
+ # The Ruby value of the string.
7
+ #
8
+ # @return [String]
9
+ attr_reader :value
10
+
11
+ # Whether this is a CSS string or a CSS identifier.
12
+ # The difference is that strings are written with double-quotes,
13
+ # while identifiers aren't.
14
+ #
15
+ # @return [Symbol] `:string` or `:identifier`
16
+ attr_reader :type
17
+
18
+ # Creates a new string.
19
+ #
20
+ # @param value [String] See \{#value}
21
+ # @param type [Symbol] See \{#type}
22
+ def initialize(value, type = :identifier)
23
+ super(value)
24
+ @type = type
25
+ end
26
+
27
+ # @see Literal#plus
28
+ def plus(other)
29
+ other_str = other.is_a?(Sass::Script::String) ? other.value : other.to_s
30
+ Sass::Script::String.new(self.value + other_str, self.type)
31
+ end
32
+
33
+ # @see Node#to_s
34
+ def to_s(opts = {})
35
+ if @type == :identifier
36
+ return @value.gsub(/\n\s*/, " ")
37
+ end
38
+
39
+ return "\"#{value.gsub('"', "\\\"")}\"" if opts[:quote] == %q{"}
40
+ return "'#{value.gsub("'", "\\'")}'" if opts[:quote] == %q{'}
41
+ return "\"#{value}\"" unless value.include?('"')
42
+ return "'#{value}'" unless value.include?("'")
43
+ "\"#{value.gsub('"', "\\\"")}\"" #'
44
+ end
45
+
46
+ # @see Node#to_sass
47
+ def to_sass(opts = {})
48
+ to_s
49
+ end
50
+ end
51
+ end
@@ -1,4 +1,4 @@
1
- module Sass::Script::Tree
1
+ module Sass::Script
2
2
  # A SassScript object representing `#{}` interpolation within a string.
3
3
  #
4
4
  # @see Interpolation
@@ -74,16 +74,15 @@ module Sass::Script::Tree
74
74
  # Evaluates the interpolation.
75
75
  #
76
76
  # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
77
- # @return [Sass::Script::Value::String]
78
- # The SassScript string that is the value of the interpolation
77
+ # @return [Sass::Script::String] The SassScript string that is the value of the interpolation
79
78
  def _perform(environment)
80
79
  res = ""
81
80
  before = @before.perform(environment)
82
81
  res << before.value
83
82
  mid = @mid.perform(environment)
84
- res << (mid.is_a?(Sass::Script::Value::String) ? mid.value : mid.to_s(:quote => :none))
83
+ res << (mid.is_a?(Sass::Script::String) ? mid.value : mid.to_s)
85
84
  res << @after.perform(environment).value
86
- opts(Sass::Script::Value::String.new(res, before.type))
85
+ opts(Sass::Script::String.new(res, before.type))
87
86
  end
88
87
 
89
88
  private
@@ -1,4 +1,4 @@
1
- module Sass::Script::Tree
1
+ module Sass::Script
2
2
  # A SassScript parse node representing a unary operation,
3
3
  # such as `-$b` or `not true`.
4
4
  #
@@ -31,7 +31,7 @@ module Sass::Script::Tree
31
31
  (operand =~ Sass::SCSS::RX::IDENT) == 0)
32
32
  operand = "(#{@operand.to_sass(opts)})"
33
33
  end
34
- op = Sass::Script::Lexer::OPERATORS_REVERSE[@operator]
34
+ op = Lexer::OPERATORS_REVERSE[@operator]
35
35
  op + (op =~ /[a-z]/ ? " " : "") + operand
36
36
  end
37
37
 
@@ -55,15 +55,15 @@ module Sass::Script::Tree
55
55
  # Evaluates the operation.
56
56
  #
57
57
  # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
58
- # @return [Sass::Script::Value] The SassScript object that is the value of the operation
58
+ # @return [Literal] The SassScript object that is the value of the operation
59
59
  # @raise [Sass::SyntaxError] if the operation is undefined for the operand
60
60
  def _perform(environment)
61
61
  operator = "unary_#{@operator}"
62
- value = @operand.perform(environment)
63
- value.send(operator)
62
+ literal = @operand.perform(environment)
63
+ literal.send(operator)
64
64
  rescue NoMethodError => e
65
65
  raise e unless e.name.to_s == operator.to_s
66
- raise Sass::SyntaxError.new("Undefined unary operation: \"#{@operator} #{value}\".")
66
+ raise Sass::SyntaxError.new("Undefined unary operation: \"#{@operator} #{literal}\".")
67
67
  end
68
68
  end
69
69
  end
@@ -0,0 +1,58 @@
1
+ module Sass
2
+ module Script
3
+ # A SassScript parse node representing a variable.
4
+ class Variable < Node
5
+ # The name of the variable.
6
+ #
7
+ # @return [String]
8
+ attr_reader :name
9
+
10
+ # The underscored name of the variable.
11
+ #
12
+ # @return [String]
13
+ attr_reader :underscored_name
14
+
15
+ # @param name [String] See \{#name}
16
+ def initialize(name)
17
+ @name = name
18
+ @underscored_name = name.gsub(/-/,"_")
19
+ super()
20
+ end
21
+
22
+ # @return [String] A string representation of the variable
23
+ def inspect(opts = {})
24
+ "$#{dasherize(name, opts)}"
25
+ end
26
+ alias_method :to_sass, :inspect
27
+
28
+ # Returns an empty array.
29
+ #
30
+ # @return [Array<Node>] empty
31
+ # @see Node#children
32
+ def children
33
+ []
34
+ end
35
+
36
+ # @see Node#deep_copy
37
+ def deep_copy
38
+ dup
39
+ end
40
+
41
+ protected
42
+
43
+ # Evaluates the variable.
44
+ #
45
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
46
+ # @return [Literal] The SassScript object that is the value of the variable
47
+ # @raise [Sass::SyntaxError] if the variable is undefined
48
+ def _perform(environment)
49
+ raise SyntaxError.new("Undefined variable: \"$#{name}\".") unless val = environment.var(name)
50
+ if val.is_a?(Number)
51
+ val = val.dup
52
+ val.original = nil
53
+ end
54
+ return val
55
+ end
56
+ end
57
+ end
58
+ end
@@ -11,7 +11,7 @@ module Sass
11
11
 
12
12
  def placeholder_selector; nil; end
13
13
  def parent_selector; nil; end
14
- def interpolation(warn_for_color = false); nil; end
14
+ def interpolation; nil; end
15
15
  def use_css_import?; true; end
16
16
 
17
17
  def block_child(context)
@@ -25,14 +25,8 @@ module Sass
25
25
  end
26
26
  end
27
27
 
28
- def nested_properties!(node)
29
- expected('expression (e.g. 1px, bold)')
30
- end
31
-
32
- def ruleset
33
- start_pos = source_position
34
- return unless (selector = selector_comma_sequence)
35
- block(node(Sass::Tree::RuleNode.new(selector, range(start_pos)), start_pos), :ruleset)
28
+ def nested_properties!(node, space)
29
+ expected('expression (e.g. 1px, bold)');
36
30
  end
37
31
 
38
32
  @sass_script_parser = Class.new(Sass::Script::CssParser)
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
1
  require 'set'
3
2
 
4
3
  module Sass
@@ -6,30 +5,16 @@ module Sass
6
5
  # The parser for SCSS.
7
6
  # It parses a string of code into a tree of {Sass::Tree::Node}s.
8
7
  class Parser
9
- # Expose for the SASS parser.
10
- attr_accessor :offset
11
-
12
8
  # @param str [String, StringScanner] The source document to parse.
13
9
  # Note that `Parser` *won't* raise a nice error message if this isn't properly parsed;
14
10
  # for that, you should use the higher-level {Sass::Engine} or {Sass::CSS}.
15
- # @param filename [String] The name of the file being parsed. Used for
16
- # warnings and source maps.
17
- # @param importer [Sass::Importers::Base] The importer used to import the
18
- # file being parsed. Used for source maps.
19
- # @param line [Fixnum] The 1-based line on which the source string appeared,
11
+ # @param filename [String] The name of the file being parsed. Used for warnings.
12
+ # @param line [Fixnum] The line on which the source string appeared,
20
13
  # if it's part of another document.
21
- # @param offset [Fixnum] The 1-based character (not byte) offset in the line on
22
- # which the source string starts. Used for error reporting and sourcemap
23
- # building.
24
- # @comment
25
- # rubocop:disable ParameterLists
26
- def initialize(str, filename, importer, line = 1, offset = 1)
27
- # rubocop:enable ParameterLists
14
+ def initialize(str, filename, line = 1)
28
15
  @template = str
29
16
  @filename = filename
30
- @importer = importer
31
17
  @line = line
32
- @offset = offset
33
18
  @strs = []
34
19
  end
35
20
 
@@ -40,7 +25,7 @@ module Sass
40
25
  def parse
41
26
  init_scanner!
42
27
  root = stylesheet
43
- expected("selector or at-rule") unless root && @scanner.eos?
28
+ expected("selector or at-rule") unless @scanner.eos?
44
29
  root
45
30
  end
46
31
 
@@ -48,7 +33,7 @@ module Sass
48
33
  # Note that this won't assert that the identifier takes up the entire input string;
49
34
  # it's meant to be used with `StringScanner`s as part of other parsers.
50
35
  #
51
- # @return [Array<String, Sass::Script::Tree::Node>, nil]
36
+ # @return [Array<String, Sass::Script::Node>, nil]
52
37
  # The interpolated identifier, or nil if none could be parsed
53
38
  def parse_interp_ident
54
39
  init_scanner!
@@ -63,22 +48,10 @@ module Sass
63
48
  def parse_media_query_list
64
49
  init_scanner!
65
50
  ql = media_query_list
66
- expected("media query list") unless ql && @scanner.eos?
51
+ expected("media query list") unless @scanner.eos?
67
52
  ql
68
53
  end
69
54
 
70
- # Parses an at-root query.
71
- #
72
- # @return [Array<String, Sass::Script;:Tree::Node>] The interpolated query.
73
- # @raise [Sass::SyntaxError] if there's a syntax error in the query,
74
- # or if it doesn't take up the entire input string.
75
- def parse_at_root_query
76
- init_scanner!
77
- query = at_root_query
78
- expected("@at-root query list") unless query && @scanner.eos?
79
- query
80
- end
81
-
82
55
  # Parses a supports query condition.
83
56
  #
84
57
  # @return [Sass::Supports::Condition] The parsed condition
@@ -87,7 +60,7 @@ module Sass
87
60
  def parse_supports_condition
88
61
  init_scanner!
89
62
  condition = supports_condition
90
- expected("supports condition") unless condition && @scanner.eos?
63
+ expected("supports condition") unless @scanner.eos?
91
64
  condition
92
65
  end
93
66
 
@@ -95,14 +68,6 @@ module Sass
95
68
 
96
69
  include Sass::SCSS::RX
97
70
 
98
- def source_position
99
- Sass::Source::Position.new(@line, @offset)
100
- end
101
-
102
- def range(start_pos, end_pos = source_position)
103
- Sass::Source::Range.new(start_pos, end_pos, @filename, @importer)
104
- end
105
-
106
71
  def init_scanner!
107
72
  @scanner =
108
73
  if @template.is_a?(StringScanner)
@@ -113,7 +78,7 @@ module Sass
113
78
  end
114
79
 
115
80
  def stylesheet
116
- node = node(Sass::Tree::RootNode.new(@scanner.string), source_position)
81
+ node = node(Sass::Tree::RootNode.new(@scanner.string))
117
82
  block_contents(node, :stylesheet) {s(node)}
118
83
  end
119
84
 
@@ -147,33 +112,21 @@ module Sass
147
112
  end
148
113
 
149
114
  def process_comment(text, node)
150
- silent = text =~ %r{\A//}
151
- loud = !silent && text =~ %r{\A/[/*]!}
115
+ silent = text =~ /^\/\//
116
+ loud = !silent && text =~ %r{^/[/*]!}
152
117
  line = @line - text.count("\n")
153
118
 
154
119
  if silent
155
- value = [text.sub(%r{\A\s*//}, '/*').gsub(%r{^\s*//}, ' *') + ' */']
120
+ value = [text.sub(/^\s*\/\//, '/*').gsub(/^\s*\/\//, ' *') + ' */']
156
121
  else
157
- value = Sass::Engine.parse_interp(
158
- text, line, @scanner.pos - text.size, :filename => @filename)
159
- string_before_comment = @scanner.string[0...@scanner.pos - text.length]
160
- newline_before_comment = string_before_comment.rindex("\n")
161
- last_line_before_comment =
162
- if newline_before_comment
163
- string_before_comment[newline_before_comment + 1..-1]
164
- else
165
- string_before_comment
166
- end
167
- value.unshift(last_line_before_comment.gsub(/[^\s]/, ' '))
122
+ value = Sass::Engine.parse_interp(text, line, @scanner.pos - text.size, :filename => @filename)
123
+ value.unshift(@scanner.
124
+ string[0...@scanner.pos].
125
+ reverse[/.*?\*\/(.*?)($|\Z)/, 1].
126
+ reverse.gsub(/[^\s]/, ' '))
168
127
  end
169
128
 
170
- type = if silent
171
- :silent
172
- elsif loud
173
- :loud
174
- else
175
- :normal
176
- end
129
+ type = if silent then :silent elsif loud then :loud else :normal end
177
130
  comment = Sass::Tree::CommentNode.new(value, type)
178
131
  comment.line = line
179
132
  node << comment
@@ -181,29 +134,31 @@ module Sass
181
134
 
182
135
  DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
183
136
  :each, :while, :if, :else, :extend, :import, :media, :charset, :content,
184
- :_moz_document, :at_root, :error]
137
+ :_moz_document]
185
138
 
186
139
  PREFIXED_DIRECTIVES = Set[:supports]
187
140
 
188
141
  def directive
189
- start_pos = source_position
190
142
  return unless tok(/@/)
191
143
  name = tok!(IDENT)
192
144
  ss
193
145
 
194
- if (dir = special_directive(name, start_pos))
146
+ if dir = special_directive(name)
195
147
  return dir
196
- elsif (dir = prefixed_directive(name, start_pos))
148
+ elsif dir = prefixed_directive(name)
197
149
  return dir
198
150
  end
199
151
 
200
- val = almost_any_value
152
+ # Most at-rules take expressions (e.g. @import),
153
+ # but some (e.g. @page) take selector-like arguments.
154
+ # Some take no arguments at all.
155
+ val = expr || selector
201
156
  val = val ? ["@#{name} "] + Sass::Util.strip_string_array(val) : ["@#{name}"]
202
- directive_body(val, start_pos)
157
+ directive_body(val)
203
158
  end
204
159
 
205
- def directive_body(value, start_pos)
206
- node = Sass::Tree::DirectiveNode.new(value)
160
+ def directive_body(value)
161
+ node = node(Sass::Tree::DirectiveNode.new(value))
207
162
 
208
163
  if tok(/\{/)
209
164
  node.has_children = true
@@ -211,32 +166,31 @@ module Sass
211
166
  tok!(/\}/)
212
167
  end
213
168
 
214
- node(node, start_pos)
169
+ node
215
170
  end
216
171
 
217
- def special_directive(name, start_pos)
172
+ def special_directive(name)
218
173
  sym = name.gsub('-', '_').to_sym
219
- DIRECTIVES.include?(sym) && send("#{sym}_directive", start_pos)
174
+ DIRECTIVES.include?(sym) && send("#{sym}_directive")
220
175
  end
221
176
 
222
- def prefixed_directive(name, start_pos)
223
- sym = deprefix(name).gsub('-', '_').to_sym
224
- PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name, start_pos)
177
+ def prefixed_directive(name)
178
+ sym = name.gsub(/^-[a-z0-9]+-/i, '').gsub('-', '_').to_sym
179
+ PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name)
225
180
  end
226
181
 
227
- def mixin_directive(start_pos)
182
+ def mixin_directive
228
183
  name = tok! IDENT
229
184
  args, splat = sass_script(:parse_mixin_definition_arglist)
230
185
  ss
231
- block(node(Sass::Tree::MixinDefNode.new(name, args, splat), start_pos), :directive)
186
+ block(node(Sass::Tree::MixinDefNode.new(name, args, splat)), :directive)
232
187
  end
233
188
 
234
- def include_directive(start_pos)
189
+ def include_directive
235
190
  name = tok! IDENT
236
- args, keywords, splat, kwarg_splat = sass_script(:parse_mixin_include_arglist)
191
+ args, keywords, splat = sass_script(:parse_mixin_include_arglist)
237
192
  ss
238
- include_node = node(
239
- Sass::Tree::MixinNode.new(name, args, keywords, splat, kwarg_splat), start_pos)
193
+ include_node = node(Sass::Tree::MixinNode.new(name, args, keywords, splat))
240
194
  if tok?(/\{/)
241
195
  include_node.has_children = true
242
196
  block(include_node, :directive)
@@ -245,31 +199,31 @@ module Sass
245
199
  end
246
200
  end
247
201
 
248
- def content_directive(start_pos)
202
+ def content_directive
249
203
  ss
250
- node(Sass::Tree::ContentNode.new, start_pos)
204
+ node(Sass::Tree::ContentNode.new)
251
205
  end
252
206
 
253
- def function_directive(start_pos)
207
+ def function_directive
254
208
  name = tok! IDENT
255
209
  args, splat = sass_script(:parse_function_definition_arglist)
256
210
  ss
257
- block(node(Sass::Tree::FunctionNode.new(name, args, splat), start_pos), :function)
211
+ block(node(Sass::Tree::FunctionNode.new(name, args, splat)), :function)
258
212
  end
259
213
 
260
- def return_directive(start_pos)
261
- node(Sass::Tree::ReturnNode.new(sass_script(:parse)), start_pos)
214
+ def return_directive
215
+ node(Sass::Tree::ReturnNode.new(sass_script(:parse)))
262
216
  end
263
217
 
264
- def debug_directive(start_pos)
265
- node(Sass::Tree::DebugNode.new(sass_script(:parse)), start_pos)
218
+ def debug_directive
219
+ node(Sass::Tree::DebugNode.new(sass_script(:parse)))
266
220
  end
267
221
 
268
- def warn_directive(start_pos)
269
- node(Sass::Tree::WarnNode.new(sass_script(:parse)), start_pos)
222
+ def warn_directive
223
+ node(Sass::Tree::WarnNode.new(sass_script(:parse)))
270
224
  end
271
225
 
272
- def for_directive(start_pos)
226
+ def for_directive
273
227
  tok!(/\$/)
274
228
  var = tok! IDENT
275
229
  ss
@@ -283,37 +237,31 @@ module Sass
283
237
  to = sass_script(:parse)
284
238
  ss
285
239
 
286
- block(node(Sass::Tree::ForNode.new(var, from, to, exclusive), start_pos), :directive)
240
+ block(node(Sass::Tree::ForNode.new(var, from, to, exclusive)), :directive)
287
241
  end
288
242
 
289
- def each_directive(start_pos)
243
+ def each_directive
290
244
  tok!(/\$/)
291
- vars = [tok!(IDENT)]
245
+ var = tok! IDENT
292
246
  ss
293
- while tok(/,/)
294
- ss
295
- tok!(/\$/)
296
- vars << tok!(IDENT)
297
- ss
298
- end
299
247
 
300
248
  tok!(/in/)
301
249
  list = sass_script(:parse)
302
250
  ss
303
251
 
304
- block(node(Sass::Tree::EachNode.new(vars, list), start_pos), :directive)
252
+ block(node(Sass::Tree::EachNode.new(var, list)), :directive)
305
253
  end
306
254
 
307
- def while_directive(start_pos)
255
+ def while_directive
308
256
  expr = sass_script(:parse)
309
257
  ss
310
- block(node(Sass::Tree::WhileNode.new(expr), start_pos), :directive)
258
+ block(node(Sass::Tree::WhileNode.new(expr)), :directive)
311
259
  end
312
260
 
313
- def if_directive(start_pos)
261
+ def if_directive
314
262
  expr = sass_script(:parse)
315
263
  ss
316
- node = block(node(Sass::Tree::IfNode.new(expr), start_pos), :directive)
264
+ node = block(node(Sass::Tree::IfNode.new(expr)), :directive)
317
265
  pos = @scanner.pos
318
266
  line = @line
319
267
  ss
@@ -328,11 +276,10 @@ module Sass
328
276
  end
329
277
 
330
278
  def else_block(node)
331
- start_pos = source_position
332
279
  return unless tok(/@else/)
333
280
  ss
334
281
  else_node = block(
335
- node(Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))), start_pos),
282
+ Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))),
336
283
  :directive)
337
284
  node.add_else(else_node)
338
285
  pos = @scanner.pos
@@ -348,20 +295,18 @@ module Sass
348
295
  end
349
296
  end
350
297
 
351
- def else_directive(start_pos)
298
+ def else_directive
352
299
  err("Invalid CSS: @else must come after @if")
353
300
  end
354
301
 
355
- def extend_directive(start_pos)
356
- selector_start_pos = source_position
357
- @expected = "selector"
358
- selector = Sass::Util.strip_string_array(expr!(:almost_any_value))
302
+ def extend_directive
303
+ selector = expr!(:selector_sequence)
359
304
  optional = tok(OPTIONAL)
360
305
  ss
361
- node(Sass::Tree::ExtendNode.new(selector, !!optional, range(selector_start_pos)), start_pos)
306
+ node(Sass::Tree::ExtendNode.new(selector, !!optional))
362
307
  end
363
308
 
364
- def import_directive(start_pos)
309
+ def import_directive
365
310
  values = []
366
311
 
367
312
  loop do
@@ -371,40 +316,42 @@ module Sass
371
316
  ss
372
317
  end
373
318
 
374
- values
319
+ return values
375
320
  end
376
321
 
377
322
  def import_arg
378
- start_pos = source_position
379
- return unless (str = string) || (uri = tok?(/url\(/i))
323
+ line = @line
324
+ return unless (str = tok(STRING)) || (uri = tok?(/url\(/i))
380
325
  if uri
381
326
  str = sass_script(:parse_string)
382
327
  ss
383
328
  media = media_query_list
384
329
  ss
385
- return node(Tree::CssImportNode.new(str, media.to_a), start_pos)
330
+ return node(Tree::CssImportNode.new(str, media.to_a))
386
331
  end
332
+
333
+ path = @scanner[1] || @scanner[2]
387
334
  ss
388
335
 
389
336
  media = media_query_list
390
- if str =~ %r{^(https?:)?//} || media || use_css_import?
391
- return node(Sass::Tree::CssImportNode.new(
392
- Sass::Script::Value::String.quote(str), media.to_a), start_pos)
337
+ if path =~ /^(https?:)?\/\// || media || use_css_import?
338
+ node = Sass::Tree::CssImportNode.new(str, media.to_a)
339
+ else
340
+ node = Sass::Tree::ImportNode.new(path.strip)
393
341
  end
394
-
395
- node(Sass::Tree::ImportNode.new(str.strip), start_pos)
342
+ node.line = line
343
+ node
396
344
  end
397
345
 
398
346
  def use_css_import?; false; end
399
347
 
400
- def media_directive(start_pos)
401
- block(node(Sass::Tree::MediaNode.new(expr!(:media_query_list).to_a), start_pos), :directive)
348
+ def media_directive
349
+ block(node(Sass::Tree::MediaNode.new(expr!(:media_query_list).to_a)), :directive)
402
350
  end
403
351
 
404
352
  # http://www.w3.org/TR/css3-mediaqueries/#syntax
405
353
  def media_query_list
406
- query = media_query
407
- return unless query
354
+ return unless query = media_query
408
355
  queries = [query]
409
356
 
410
357
  ss
@@ -417,7 +364,7 @@ module Sass
417
364
  end
418
365
 
419
366
  def media_query
420
- if (ident1 = interp_ident)
367
+ if ident1 = interp_ident
421
368
  ss
422
369
  ident2 = interp_ident
423
370
  ss
@@ -437,8 +384,7 @@ module Sass
437
384
  if query
438
385
  expr = expr!(:media_expr)
439
386
  else
440
- expr = media_expr
441
- return unless expr
387
+ return unless expr = media_expr
442
388
  end
443
389
  query ||= Sass::Media::Query.new([], [], [])
444
390
  query.expressions << expr
@@ -451,9 +397,8 @@ module Sass
451
397
  query
452
398
  end
453
399
 
454
- def query_expr
455
- interp = interpolation
456
- return interp if interp
400
+ def media_expr
401
+ interp = interpolation and return interp
457
402
  return unless tok(/\(/)
458
403
  res = ['(']
459
404
  ss
@@ -469,15 +414,11 @@ module Sass
469
414
  res
470
415
  end
471
416
 
472
- # Aliases allow us to use different descriptions if the same
473
- # expression fails in different contexts.
474
- alias_method :media_expr, :query_expr
475
- alias_method :at_root_query, :query_expr
476
-
477
- def charset_directive(start_pos)
478
- name = expr!(:string)
417
+ def charset_directive
418
+ tok! STRING
419
+ name = @scanner[1] || @scanner[2]
479
420
  ss
480
- node(Sass::Tree::CharsetNode.new(name), start_pos)
421
+ node(Sass::Tree::CharsetNode.new(name))
481
422
  end
482
423
 
483
424
  # The document directive is specified in
@@ -488,65 +429,34 @@ module Sass
488
429
  # We could parse all document directives according to Mozilla's syntax,
489
430
  # but if someone's using e.g. @-webkit-document we don't want them to
490
431
  # think WebKit works sans quotes.
491
- def _moz_document_directive(start_pos)
432
+ def _moz_document_directive
492
433
  res = ["@-moz-document "]
493
434
  loop do
494
- res << str {ss} << expr!(:moz_document_function)
495
- if (c = tok(/,/))
496
- res << c
497
- else
498
- break
499
- end
435
+ res << str{ss} << expr!(:moz_document_function)
436
+ break unless c = tok(/,/)
437
+ res << c
500
438
  end
501
- directive_body(res.flatten, start_pos)
439
+ directive_body(res.flatten)
502
440
  end
503
441
 
504
442
  def moz_document_function
505
- val = interp_uri || _interp_string(:url_prefix) ||
443
+ return unless val = interp_uri || _interp_string(:url_prefix) ||
506
444
  _interp_string(:domain) || function(!:allow_var) || interpolation
507
- return unless val
508
445
  ss
509
446
  val
510
447
  end
511
448
 
512
- def at_root_directive(start_pos)
513
- if tok?(/\(/) && (expr = at_root_query)
514
- return block(node(Sass::Tree::AtRootNode.new(expr), start_pos), :directive)
515
- end
516
-
517
- at_root_node = node(Sass::Tree::AtRootNode.new, start_pos)
518
- rule_node = ruleset
519
- return block(at_root_node, :stylesheet) unless rule_node
520
- at_root_node << rule_node
521
- at_root_node
522
- end
523
-
524
- def at_root_directive_list
525
- return unless (first = tok(IDENT))
526
- arr = [first]
527
- ss
528
- while (e = tok(IDENT))
529
- arr << e
530
- ss
531
- end
532
- arr
533
- end
534
-
535
- def error_directive(start_pos)
536
- node(Sass::Tree::ErrorNode.new(sass_script(:parse)), start_pos)
537
- end
538
-
539
449
  # http://www.w3.org/TR/css3-conditional/
540
- def supports_directive(name, start_pos)
450
+ def supports_directive(name)
541
451
  condition = expr!(:supports_condition)
542
- node = Sass::Tree::SupportsNode.new(name, condition)
452
+ node = node(Sass::Tree::SupportsNode.new(name, condition))
543
453
 
544
454
  tok!(/\{/)
545
455
  node.has_children = true
546
456
  block_contents(node, :directive)
547
457
  tok!(/\}/)
548
458
 
549
- node(node, start_pos)
459
+ node
550
460
  end
551
461
 
552
462
  def supports_condition
@@ -560,21 +470,20 @@ module Sass
560
470
  end
561
471
 
562
472
  def supports_operator
563
- cond = supports_condition_in_parens
564
- return unless cond
565
- while (op = tok(/and|or/i))
473
+ return unless cond = supports_condition_in_parens
474
+ return cond unless op = tok(/and|or/i)
475
+ begin
566
476
  ss
567
477
  cond = Sass::Supports::Operator.new(
568
478
  cond, expr!(:supports_condition_in_parens), op)
569
- end
479
+ end while op = tok(/and|or/i)
570
480
  cond
571
481
  end
572
482
 
573
483
  def supports_condition_in_parens
574
- interp = supports_interpolation
575
- return interp if interp
484
+ interp = supports_interpolation and return interp
576
485
  return unless tok(/\(/); ss
577
- if (cond = supports_condition)
486
+ if cond = supports_condition
578
487
  tok!(/\)/); ss
579
488
  cond
580
489
  else
@@ -592,33 +501,19 @@ module Sass
592
501
  end
593
502
 
594
503
  def supports_interpolation
595
- interp = interpolation
596
- return unless interp
504
+ return unless interp = interpolation
597
505
  ss
598
506
  Sass::Supports::Interpolation.new(interp)
599
507
  end
600
508
 
601
509
  def variable
602
510
  return unless tok(/\$/)
603
- start_pos = source_position
604
511
  name = tok!(IDENT)
605
512
  ss; tok!(/:/); ss
606
513
 
607
514
  expr = sass_script(:parse)
608
- while tok(/!/)
609
- flag_name = tok!(IDENT)
610
- if flag_name == 'default'
611
- guarded ||= true
612
- elsif flag_name == 'global'
613
- global ||= true
614
- else
615
- raise Sass::SyntaxError.new("Invalid flag \"!#{flag_name}\".", :line => @line)
616
- end
617
- ss
618
- end
619
-
620
- result = Sass::Tree::VariableNode.new(name, expr, guarded, global)
621
- node(result, start_pos)
515
+ guarded = tok(DEFAULT)
516
+ node(Sass::Tree::VariableNode.new(name, expr, guarded))
622
517
  end
623
518
 
624
519
  def operator
@@ -630,10 +525,8 @@ module Sass
630
525
  end
631
526
 
632
527
  def ruleset
633
- start_pos = source_position
634
- return unless (rules = almost_any_value)
635
- block(node(
636
- Sass::Tree::RuleNode.new(rules, range(start_pos)), start_pos), :ruleset)
528
+ return unless rules = selector_sequence
529
+ block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)), :ruleset)
637
530
  end
638
531
 
639
532
  def block(node, context)
@@ -664,239 +557,355 @@ module Sass
664
557
  def has_children?(child_or_array)
665
558
  return false unless child_or_array
666
559
  return child_or_array.last.has_children if child_or_array.is_a?(Array)
667
- child_or_array.has_children
560
+ return child_or_array.has_children
668
561
  end
669
562
 
670
- # When parsing the contents of a ruleset, it can be difficult to tell
671
- # declarations apart from nested rulesets. Since we don't thoroughly parse
672
- # selectors until after resolving interpolation, we can share a bunch of
673
- # the parsing of the two, but we need to disambiguate them first. We use
674
- # the following criteria:
675
- #
676
- # * If the entity doesn't start with an identifier followed by a colon,
677
- # it's a selector. There are some additional mostly-unimportant cases
678
- # here to support various declaration hacks.
679
- #
680
- # * If the colon is followed by another colon, it's a selector.
563
+ # This is a nasty hack, and the only place in the parser
564
+ # that requires a large amount of backtracking.
565
+ # The reason is that we can't figure out if certain strings
566
+ # are declarations or rulesets with fixed finite lookahead.
567
+ # For example, "foo:bar baz baz baz..." could be either a property
568
+ # or a selector.
681
569
  #
682
- # * Otherwise, if the colon is followed by anything other than
683
- # interpolation or a character that's valid as the beginning of an
684
- # identifier, it's a declaration.
570
+ # To handle this, we simply check if it works as a property
571
+ # (which is the most common case)
572
+ # and, if it doesn't, try it as a ruleset.
685
573
  #
686
- # * If the colon is followed by interpolation or a valid identifier, try
687
- # parsing it as a declaration value. If this fails, backtrack and parse
688
- # it as a selector.
689
- #
690
- # * If the declaration value value valid but is followed by "{", backtrack
691
- # and parse it as a selector anyway. This ensures that ".foo:bar {" is
692
- # always parsed as a selector and never as a property with nested
693
- # properties beneath it.
574
+ # We could eke some more efficiency out of this
575
+ # by handling some easy cases (first token isn't an identifier,
576
+ # no colon after the identifier, whitespace after the colon),
577
+ # but I'm not sure the gains would be worth the added complexity.
694
578
  def declaration_or_ruleset
695
- start_pos = source_position
696
- declaration = try_declaration
579
+ old_use_property_exception, @use_property_exception =
580
+ @use_property_exception, false
581
+ decl_err = catch_error do
582
+ decl = declaration
583
+ unless decl && decl.has_children
584
+ # We want an exception if it's not there,
585
+ # but we don't want to consume if it is
586
+ tok!(/[;}]/) unless tok?(/[;}]/)
587
+ end
588
+ return decl
589
+ end
697
590
 
698
- if declaration.nil?
699
- return unless (selector = almost_any_value)
700
- elsif declaration.is_a?(Array)
701
- selector = declaration
702
- else
703
- # Declaration should be a PropNode.
704
- return declaration
591
+ ruleset_err = catch_error {return ruleset}
592
+ rethrow(@use_property_exception ? decl_err : ruleset_err)
593
+ ensure
594
+ @use_property_exception = old_use_property_exception
595
+ end
596
+
597
+ def selector_sequence
598
+ if sel = tok(STATIC_SELECTOR, true)
599
+ return [sel]
705
600
  end
706
601
 
707
- if (additional_selector = almost_any_value)
708
- selector << additional_selector
602
+ rules = []
603
+ return unless v = selector
604
+ rules.concat v
605
+
606
+ ws = ''
607
+ while tok(/,/)
608
+ ws << str {ss}
609
+ if v = selector
610
+ rules << ',' << ws
611
+ rules.concat v
612
+ ws = ''
613
+ end
709
614
  end
615
+ rules
616
+ end
710
617
 
711
- block(node(
712
- Sass::Tree::RuleNode.new(merge(selector), range(start_pos)), start_pos), :ruleset)
618
+ def selector
619
+ return unless sel = _selector
620
+ sel.to_a
713
621
  end
714
622
 
715
- # Tries to parse a declaration, and returns the value parsed so far if it
716
- # fails.
717
- #
718
- # This has three possible return types. It can return `nil`, indicating
719
- # that parsing failed completely and the scanner hasn't moved forward at
720
- # all. It can return an Array, indicating that parsing failed after
721
- # consuming some text (possibly containing interpolation), which is
722
- # returned. Or it can return a PropNode, indicating that parsing
723
- # succeeded.
724
- def try_declaration
725
- # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop:
726
- # val" hacks.
727
- name_start_pos = source_position
728
- if (s = tok(/[:\*\.]|\#(?!\{)/))
729
- name = [s, str {ss}]
730
- return name unless (ident = interp_ident)
731
- name << ident
732
- else
733
- return unless (name = interp_ident)
734
- name = Array(name)
623
+ def selector_comma_sequence
624
+ return unless sel = _selector
625
+ selectors = [sel]
626
+ ws = ''
627
+ while tok(/,/)
628
+ ws << str{ss}
629
+ if sel = _selector
630
+ selectors << sel
631
+ selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members) if ws.include?("\n")
632
+ ws = ''
633
+ end
735
634
  end
635
+ Selector::CommaSequence.new(selectors)
636
+ end
736
637
 
737
- if (comment = tok(COMMENT))
738
- name << comment
638
+ def _selector
639
+ # The combinator here allows the "> E" hack
640
+ return unless val = combinator || simple_selector_sequence
641
+ nl = str{ss}.include?("\n")
642
+ res = []
643
+ res << val
644
+ res << "\n" if nl
645
+
646
+ while val = combinator || simple_selector_sequence
647
+ res << val
648
+ res << "\n" if str{ss}.include?("\n")
649
+ end
650
+ Selector::Sequence.new(res.compact)
651
+ end
652
+
653
+ def combinator
654
+ tok(PLUS) || tok(GREATER) || tok(TILDE) || reference_combinator
655
+ end
656
+
657
+ def reference_combinator
658
+ return unless tok(/\//)
659
+ res = ['/']
660
+ ns, name = expr!(:qualified_name)
661
+ res << ns << '|' if ns
662
+ res << name << tok!(/\//)
663
+ res = res.flatten
664
+ res = res.join '' if res.all? {|e| e.is_a?(String)}
665
+ res
666
+ end
667
+
668
+ def simple_selector_sequence
669
+ # Returning expr by default allows for stuff like
670
+ # http://www.w3.org/TR/css3-animations/#keyframes-
671
+ return expr(!:allow_var) unless e = element_name || id_selector ||
672
+ class_selector || placeholder_selector || attrib || pseudo ||
673
+ parent_selector || interpolation_selector
674
+ res = [e]
675
+
676
+ # The tok(/\*/) allows the "E*" hack
677
+ while v = id_selector || class_selector || placeholder_selector || attrib ||
678
+ pseudo || interpolation_selector ||
679
+ (tok(/\*/) && Selector::Universal.new(nil))
680
+ res << v
739
681
  end
740
- name_end_pos = source_position
741
-
742
- mid = [str {ss}]
743
- return name + mid unless tok(/:/)
744
- mid << ':'
745
- return name + mid + [':'] if tok(/:/)
746
- mid << str {ss}
747
- post_colon_whitespace = !mid.last.empty?
748
- could_be_selector = !post_colon_whitespace && (tok?(IDENT_START) || tok?(INTERP_START))
749
-
750
- value_start_pos = source_position
751
- value = nil
752
- error = catch_error do
753
- value = value!
754
- if tok?(/\{/)
755
- # Properties that are ambiguous with selectors can't have additional
756
- # properties nested beneath them.
757
- tok!(/;/) if could_be_selector
758
- elsif !tok?(/[;{}]/)
759
- # We want an exception if there's no valid end-of-property character
760
- # exists, but we don't want to consume it if it does.
761
- tok!(/[;{}]/)
682
+
683
+ pos = @scanner.pos
684
+ line = @line
685
+ if sel = str? {simple_selector_sequence}
686
+ @scanner.pos = pos
687
+ @line = line
688
+ begin
689
+ # If we see "*E", don't force a throw because this could be the
690
+ # "*prop: val" hack.
691
+ expected('"{"') if res.length == 1 && res[0].is_a?(Selector::Universal)
692
+ throw_error {expected('"{"')}
693
+ rescue Sass::SyntaxError => e
694
+ e.message << "\n\n\"#{sel}\" may only be used at the beginning of a compound selector."
695
+ raise e
762
696
  end
763
697
  end
764
698
 
765
- if error
766
- rethrow error unless could_be_selector
699
+ Selector::SimpleSequence.new(res, tok(/!/))
700
+ end
701
+
702
+ def parent_selector
703
+ return unless tok(/&/)
704
+ Selector::Parent.new
705
+ end
706
+
707
+ def class_selector
708
+ return unless tok(/\./)
709
+ @expected = "class name"
710
+ Selector::Class.new(merge(expr!(:interp_ident)))
711
+ end
767
712
 
768
- # If the value would be followed by a semicolon, it's definitely
769
- # supposed to be a property, not a selector.
770
- additional_selector = almost_any_value
771
- rethrow error if tok?(/;/)
713
+ def id_selector
714
+ return unless tok(/#(?!\{)/)
715
+ @expected = "id name"
716
+ Selector::Id.new(merge(expr!(:interp_name)))
717
+ end
718
+
719
+ def placeholder_selector
720
+ return unless tok(/%/)
721
+ @expected = "placeholder name"
722
+ Selector::Placeholder.new(merge(expr!(:interp_ident)))
723
+ end
772
724
 
773
- return name + mid + (additional_selector || [])
725
+ def element_name
726
+ ns, name = Sass::Util.destructure(qualified_name(:allow_star_name))
727
+ return unless ns || name
728
+
729
+ if name == '*'
730
+ Selector::Universal.new(merge(ns))
731
+ else
732
+ Selector::Element.new(merge(name), merge(ns))
774
733
  end
734
+ end
735
+
736
+ def qualified_name(allow_star_name=false)
737
+ return unless name = interp_ident || tok(/\*/) || (tok?(/\|/) && "")
738
+ return nil, name unless tok(/\|/)
775
739
 
776
- value_end_pos = source_position
740
+ return name, expr!(:interp_ident) unless allow_star_name
741
+ @expected = "identifier or *"
742
+ return name, interp_ident || tok!(/\*/)
743
+ end
744
+
745
+ def interpolation_selector
746
+ return unless script = interpolation
747
+ Selector::Interpolation.new(script)
748
+ end
749
+
750
+ def attrib
751
+ return unless tok(/\[/)
752
+ ss
753
+ ns, name = attrib_name!
777
754
  ss
778
- require_block = tok?(/\{/)
779
755
 
780
- node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new),
781
- name_start_pos, value_end_pos)
782
- node.name_source_range = range(name_start_pos, name_end_pos)
783
- node.value_source_range = range(value_start_pos, value_end_pos)
756
+ if op = tok(/=/) ||
757
+ tok(INCLUDES) ||
758
+ tok(DASHMATCH) ||
759
+ tok(PREFIXMATCH) ||
760
+ tok(SUFFIXMATCH) ||
761
+ tok(SUBSTRINGMATCH)
762
+ @expected = "identifier or string"
763
+ ss
764
+ val = interp_ident || expr!(:interp_string)
765
+ ss
766
+ end
767
+ flags = interp_ident || interp_string
768
+ tok!(/\]/)
784
769
 
785
- return node unless require_block
786
- nested_properties! node
770
+ Selector::Attribute.new(merge(name), merge(ns), op, merge(val), merge(flags))
787
771
  end
788
772
 
789
- # This production is similar to the CSS [`<any-value>`][any-value]
790
- # production, but as the name implies, not quite the same. It's meant to
791
- # consume values that could be a selector, an expression, or a combination
792
- # of both. It respects strings and comments and supports interpolation. It
793
- # will consume up to "{", "}", ";", or "!".
794
- #
795
- # [any-value]: http://dev.w3.org/csswg/css-variables/#typedef-any-value
796
- #
797
- # Values consumed by this production will usually be parsed more
798
- # thoroughly once interpolation has been resolved.
799
- def almost_any_value
800
- return unless (tok = almost_any_value_token)
801
- sel = [tok]
802
- while (tok = almost_any_value_token)
803
- sel << tok
773
+ def attrib_name!
774
+ if name_or_ns = interp_ident
775
+ # E, E|E
776
+ if tok(/\|(?!=)/)
777
+ ns = name_or_ns
778
+ name = interp_ident
779
+ else
780
+ name = name_or_ns
781
+ end
782
+ else
783
+ # *|E or |E
784
+ ns = [tok(/\*/) || ""]
785
+ tok!(/\|/)
786
+ name = expr!(:interp_ident)
804
787
  end
805
- merge(sel)
788
+ return ns, name
806
789
  end
807
790
 
808
- def almost_any_value_token
809
- tok(%r{
810
- (
811
- (?!url\()
812
- [^"/\#!;\{\}] # "
813
- |
814
- /(?![/*])
815
- |
816
- \#(?!\{)
817
- |
818
- !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed.
819
- )+
820
- }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri ||
821
- interpolation(:warn_for_color)
791
+ def pseudo
792
+ return unless s = tok(/::?/)
793
+ @expected = "pseudoclass or pseudoelement"
794
+ name = expr!(:interp_ident)
795
+ if tok(/\(/)
796
+ ss
797
+ arg = expr!(:pseudo_arg)
798
+ while tok(/,/)
799
+ arg << ',' << str{ss}
800
+ arg.concat expr!(:pseudo_arg)
801
+ end
802
+ tok!(/\)/)
803
+ end
804
+ Selector::Pseudo.new(s == ':' ? :class : :element, merge(name), merge(arg))
805
+ end
806
+
807
+ def pseudo_arg
808
+ # In the CSS spec, every pseudo-class/element either takes a pseudo
809
+ # expression or a selector comma sequence as an argument. However, we
810
+ # don't want to have to know which takes which, so we handle both at
811
+ # once.
812
+ #
813
+ # However, there are some ambiguities between the two. For instance, "n"
814
+ # could start a pseudo expression like "n+1", or it could start a
815
+ # selector like "n|m". In order to handle this, we must regrettably
816
+ # backtrack.
817
+ expr, sel = nil, nil
818
+ pseudo_err = catch_error do
819
+ expr = pseudo_expr
820
+ next if tok?(/[,)]/)
821
+ expr = nil
822
+ expected '")"'
823
+ end
824
+
825
+ return expr if expr
826
+ sel_err = catch_error {sel = selector}
827
+ return sel if sel
828
+ rethrow pseudo_err if pseudo_err
829
+ rethrow sel_err if sel_err
830
+ return
831
+ end
832
+
833
+ def pseudo_expr
834
+ return unless e = tok(PLUS) || tok(/[-*]/) || tok(NUMBER) ||
835
+ interp_string || tok(IDENT) || interpolation
836
+ res = [e, str{ss}]
837
+ while e = tok(PLUS) || tok(/[-*]/) || tok(NUMBER) ||
838
+ interp_string || tok(IDENT) || interpolation
839
+ res << e << str{ss}
840
+ end
841
+ res
822
842
  end
823
843
 
824
844
  def declaration
825
- # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop:
826
- # val" hacks.
827
- name_start_pos = source_position
828
- if (s = tok(/[:\*\.]|\#(?!\{)/))
829
- name = [s, str {ss}, *expr!(:interp_ident)]
845
+ # This allows the "*prop: val", ":prop: val", and ".prop: val" hacks
846
+ if s = tok(/[:\*\.]|\#(?!\{)/)
847
+ @use_property_exception = s !~ /[\.\#]/
848
+ name = [s, str{ss}, *expr!(:interp_ident)]
830
849
  else
831
- return unless (name = interp_ident)
832
- name = Array(name)
850
+ return unless name = interp_ident
851
+ name = [name] if name.is_a?(String)
833
852
  end
834
-
835
- if (comment = tok(COMMENT))
853
+ if comment = tok(COMMENT)
836
854
  name << comment
837
855
  end
838
- name_end_pos = source_position
839
856
  ss
840
857
 
841
858
  tok!(/:/)
842
- ss
843
- value_start_pos = source_position
844
- value = value!
845
- value_end_pos = source_position
859
+ space, value = value!
846
860
  ss
847
861
  require_block = tok?(/\{/)
848
862
 
849
- node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new),
850
- name_start_pos, value_end_pos)
851
- node.name_source_range = range(name_start_pos, name_end_pos)
852
- node.value_source_range = range(value_start_pos, value_end_pos)
863
+ node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new))
853
864
 
854
865
  return node unless require_block
855
- nested_properties! node
866
+ nested_properties! node, space
856
867
  end
857
868
 
858
869
  def value!
859
- if tok?(/\{/)
860
- str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(""))
861
- str.line = source_position.line
862
- str.source_range = range(source_position)
863
- return str
864
- end
870
+ space = !str {ss}.empty?
871
+ @use_property_exception ||= space || !tok?(IDENT)
865
872
 
866
- start_pos = source_position
873
+ return true, Sass::Script::String.new("") if tok?(/\{/)
867
874
  # This is a bit of a dirty trick:
868
875
  # if the value is completely static,
869
876
  # we don't parse it at all, and instead return a plain old string
870
877
  # containing the value.
871
878
  # This results in a dramatic speed increase.
872
- if (val = tok(STATIC_VALUE, true))
873
- str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(val.strip))
874
- str.line = start_pos.line
875
- str.source_range = range(start_pos)
876
- return str
879
+ if val = tok(STATIC_VALUE, true)
880
+ return space, Sass::Script::String.new(val.strip)
877
881
  end
878
- sass_script(:parse)
882
+ return space, sass_script(:parse)
879
883
  end
880
884
 
881
- def nested_properties!(node)
885
+ def nested_properties!(node, space)
886
+ err(<<MESSAGE) unless space
887
+ Invalid CSS: a space is required between a property and its definition
888
+ when it has other properties nested beneath it.
889
+ MESSAGE
890
+
891
+ @use_property_exception = true
882
892
  @expected = 'expression (e.g. 1px, bold) or "{"'
883
893
  block(node, :property)
884
894
  end
885
895
 
886
896
  def expr(allow_var = true)
887
- t = term(allow_var)
888
- return unless t
889
- res = [t, str {ss}]
897
+ return unless t = term(allow_var)
898
+ res = [t, str{ss}]
890
899
 
891
900
  while (o = operator) && (t = term(allow_var))
892
- res << o << t << str {ss}
901
+ res << o << t << str{ss}
893
902
  end
894
903
 
895
904
  res.flatten
896
905
  end
897
906
 
898
907
  def term(allow_var)
899
- e = tok(NUMBER) ||
908
+ if e = tok(NUMBER) ||
900
909
  interp_uri ||
901
910
  function(allow_var) ||
902
911
  interp_string ||
@@ -904,42 +913,36 @@ module Sass
904
913
  interp_ident ||
905
914
  tok(HEXCOLOR) ||
906
915
  (allow_var && var_expr)
907
- return e if e
916
+ return e
917
+ end
908
918
 
909
- op = tok(/[+-]/)
910
- return unless op
919
+ return unless op = tok(/[+-]/)
911
920
  @expected = "number or function"
912
- [op,
913
- tok(NUMBER) || function(allow_var) || (allow_var && var_expr) || expr!(:interpolation)]
921
+ return [op, tok(NUMBER) || function(allow_var) ||
922
+ (allow_var && var_expr) || expr!(:interpolation)]
914
923
  end
915
924
 
916
925
  def function(allow_var)
917
- name = tok(FUNCTION)
918
- return unless name
926
+ return unless name = tok(FUNCTION)
919
927
  if name == "expression(" || name == "calc("
920
928
  str, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
921
929
  [name, str]
922
930
  else
923
- [name, str {ss}, expr(allow_var), tok!(/\)/)]
931
+ [name, str{ss}, expr(allow_var), tok!(/\)/)]
924
932
  end
925
933
  end
926
934
 
927
935
  def var_expr
928
936
  return unless tok(/\$/)
929
937
  line = @line
930
- var = Sass::Script::Tree::Variable.new(tok!(IDENT))
938
+ var = Sass::Script::Variable.new(tok!(IDENT))
931
939
  var.line = line
932
940
  var
933
941
  end
934
942
 
935
- def interpolation(warn_for_color = false)
943
+ def interpolation
936
944
  return unless tok(INTERP_START)
937
- sass_script(:parse_interpolated, warn_for_color)
938
- end
939
-
940
- def string
941
- return unless tok(STRING)
942
- Sass::Script::Value::String.value(@scanner[1] || @scanner[2])
945
+ sass_script(:parse_interpolated)
943
946
  end
944
947
 
945
948
  def interp_string
@@ -951,8 +954,7 @@ module Sass
951
954
  end
952
955
 
953
956
  def _interp_string(type)
954
- start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][false])
955
- return unless start
957
+ return unless start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][false])
956
958
  res = [start]
957
959
 
958
960
  mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][true]
@@ -966,20 +968,21 @@ module Sass
966
968
  end
967
969
 
968
970
  def interp_ident(start = IDENT)
969
- val = tok(start) || interpolation(:warn_for_color) || tok(IDENT_HYPHEN_INTERP, true)
970
- return unless val
971
+ return unless val = tok(start) || interpolation || tok(IDENT_HYPHEN_INTERP, true)
971
972
  res = [val]
972
- while (val = tok(NAME) || interpolation(:warn_for_color))
973
+ while val = tok(NAME) || interpolation
973
974
  res << val
974
975
  end
975
976
  res
976
977
  end
977
978
 
978
979
  def interp_ident_or_var
979
- id = interp_ident
980
- return id if id
981
- var = var_expr
982
- return [var] if var
980
+ (id = interp_ident) and return id
981
+ (var = var_expr) and return [var]
982
+ end
983
+
984
+ def interp_name
985
+ interp_ident NAME
983
986
  end
984
987
 
985
988
  def str
@@ -993,35 +996,29 @@ module Sass
993
996
  def str?
994
997
  pos = @scanner.pos
995
998
  line = @line
996
- offset = @offset
997
999
  @strs.push ""
998
1000
  throw_error {yield} && @strs.last
999
1001
  rescue Sass::SyntaxError
1000
1002
  @scanner.pos = pos
1001
1003
  @line = line
1002
- @offset = offset
1003
1004
  nil
1004
1005
  ensure
1005
1006
  @strs.pop
1006
1007
  end
1007
1008
 
1008
- def node(node, start_pos, end_pos = source_position)
1009
- node.line = start_pos.line
1010
- node.source_range = range(start_pos, end_pos)
1009
+ def node(node)
1010
+ node.line = @line
1011
1011
  node
1012
1012
  end
1013
1013
 
1014
1014
  @sass_script_parser = Class.new(Sass::Script::Parser)
1015
1015
  @sass_script_parser.send(:include, ScriptParser)
1016
-
1017
- class << self
1018
- # @private
1019
- attr_accessor :sass_script_parser
1020
- end
1016
+ # @private
1017
+ def self.sass_script_parser; @sass_script_parser; end
1021
1018
 
1022
1019
  def sass_script(*args)
1023
- parser = self.class.sass_script_parser.new(@scanner, @line, @offset,
1024
- :filename => @filename, :importer => @importer)
1020
+ parser = self.class.sass_script_parser.new(@scanner, @line,
1021
+ @scanner.pos - (@scanner.string[0...@scanner.pos].rindex("\n") || 0))
1025
1022
  result = parser.send(*args)
1026
1023
  unless @strs.empty?
1027
1024
  # Convert to CSS manually so that comments are ignored.
@@ -1029,7 +1026,6 @@ module Sass
1029
1026
  @strs.each {|s| s << src}
1030
1027
  end
1031
1028
  @line = parser.line
1032
- @offset = parser.offset
1033
1029
  result
1034
1030
  rescue Sass::SyntaxError => e
1035
1031
  throw(:_sass_parser_error, true) if @throw_error
@@ -1044,51 +1040,41 @@ module Sass
1044
1040
  :media_query => "media query (e.g. print, screen, print and screen)",
1045
1041
  :media_query_list => "media query (e.g. print, screen, print and screen)",
1046
1042
  :media_expr => "media expression (e.g. (min-device-width: 800px))",
1047
- :at_root_query => "@at-root query (e.g. (without: media))",
1048
- :at_root_directive_list => '* or identifier',
1049
- :pseudo_args => "expression (e.g. fr, 2n+1)",
1043
+ :pseudo_arg => "expression (e.g. fr, 2n+1)",
1050
1044
  :interp_ident => "identifier",
1045
+ :interp_name => "identifier",
1051
1046
  :qualified_name => "identifier",
1052
1047
  :expr => "expression (e.g. 1px, bold)",
1048
+ :_selector => "selector",
1053
1049
  :selector_comma_sequence => "selector",
1054
- :string => "string",
1050
+ :simple_selector_sequence => "selector",
1055
1051
  :import_arg => "file to import (string or url())",
1056
1052
  :moz_document_function => "matching function (e.g. url-prefix(), domain())",
1057
1053
  :supports_condition => "@supports condition (e.g. (display: flexbox))",
1058
1054
  :supports_condition_in_parens => "@supports condition (e.g. (display: flexbox))",
1059
- :a_n_plus_b => "An+B expression",
1060
- :keyframes_selector_component => "from, to, or a percentage",
1061
- :keyframes_selector => "keyframes selector (e.g. 10%)"
1062
1055
  }
1063
1056
 
1064
- TOK_NAMES = Sass::Util.to_hash(Sass::SCSS::RX.constants.map do |c|
1065
- [Sass::SCSS::RX.const_get(c), c.downcase]
1066
- end).merge(
1067
- IDENT => "identifier",
1068
- /[;{}]/ => '";"',
1069
- /\b(without|with)\b/ => '"with" or "without"'
1070
- )
1057
+ TOK_NAMES = Sass::Util.to_hash(
1058
+ Sass::SCSS::RX.constants.map {|c| [Sass::SCSS::RX.const_get(c), c.downcase]}).
1059
+ merge(IDENT => "identifier", /[;}]/ => '";"')
1071
1060
 
1072
1061
  def tok?(rx)
1073
1062
  @scanner.match?(rx)
1074
1063
  end
1075
1064
 
1076
1065
  def expr!(name)
1077
- e = send(name)
1078
- return e if e
1066
+ (e = send(name)) && (return e)
1079
1067
  expected(EXPR_NAMES[name] || name.to_s)
1080
1068
  end
1081
1069
 
1082
1070
  def tok!(rx)
1083
- t = tok(rx)
1084
- return t if t
1071
+ (t = tok(rx)) && (return t)
1085
1072
  name = TOK_NAMES[rx]
1086
1073
 
1087
1074
  unless name
1088
1075
  # Display basic regexps as plain old strings
1089
- source = rx.source.gsub(/\\\//, '/')
1090
1076
  string = rx.source.gsub(/\\(.)/, '\1')
1091
- name = source == Regexp.escape(string) ? string.inspect : rx.inspect
1077
+ name = rx.source == Regexp.escape(string) ? string.inspect : rx.inspect
1092
1078
  end
1093
1079
 
1094
1080
  expected(name)
@@ -1115,12 +1101,10 @@ module Sass
1115
1101
  old_throw_error, @throw_error = @throw_error, true
1116
1102
  pos = @scanner.pos
1117
1103
  line = @line
1118
- offset = @offset
1119
1104
  expected = @expected
1120
1105
  if catch(:_sass_parser_error) {yield; false}
1121
1106
  @scanner.pos = pos
1122
1107
  @line = line
1123
- @offset = offset
1124
1108
  @expected = expected
1125
1109
  {:pos => pos, :line => line, :expected => @expected, :block => block}
1126
1110
  end
@@ -1183,15 +1167,7 @@ module Sass
1183
1167
  @scanner.pos -= @scanner[-1].length
1184
1168
  res.slice!(-@scanner[-1].length..-1)
1185
1169
  end
1186
-
1187
- newline_count = res.count(NEWLINE)
1188
- if newline_count > 0
1189
- @line += newline_count
1190
- @offset = res[res.rindex(NEWLINE)..-1].size
1191
- else
1192
- @offset += res.size
1193
- end
1194
-
1170
+ @line += res.count(NEWLINE)
1195
1171
  @expected = nil
1196
1172
  if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
1197
1173
  @strs.each {|s| s << res}
@@ -1199,11 +1175,6 @@ module Sass
1199
1175
  res
1200
1176
  end
1201
1177
  end
1202
-
1203
- # Remove a vendor prefix from `str`.
1204
- def deprefix(str)
1205
- str.gsub(/^-[a-zA-Z0-9]+-/, '')
1206
- end
1207
1178
  end
1208
1179
  end
1209
1180
  end