sass 3.1.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (260) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING +1 -1
  3. data/MIT-LICENSE +2 -2
  4. data/README.md +29 -17
  5. data/Rakefile +43 -9
  6. data/VERSION +1 -1
  7. data/VERSION_DATE +1 -0
  8. data/VERSION_NAME +1 -1
  9. data/bin/sass +6 -1
  10. data/bin/sass-convert +6 -1
  11. data/bin/scss +6 -1
  12. data/ext/mkrf_conf.rb +27 -0
  13. data/lib/sass/cache_stores/base.rb +7 -3
  14. data/lib/sass/cache_stores/chain.rb +3 -2
  15. data/lib/sass/cache_stores/filesystem.rb +5 -7
  16. data/lib/sass/cache_stores/memory.rb +1 -1
  17. data/lib/sass/cache_stores/null.rb +2 -2
  18. data/lib/sass/callbacks.rb +2 -1
  19. data/lib/sass/css.rb +168 -53
  20. data/lib/sass/engine.rb +502 -174
  21. data/lib/sass/environment.rb +151 -111
  22. data/lib/sass/error.rb +7 -7
  23. data/lib/sass/exec.rb +176 -60
  24. data/lib/sass/features.rb +40 -0
  25. data/lib/sass/importers/base.rb +46 -7
  26. data/lib/sass/importers/deprecated_path.rb +51 -0
  27. data/lib/sass/importers/filesystem.rb +113 -30
  28. data/lib/sass/importers.rb +1 -0
  29. data/lib/sass/logger/base.rb +30 -0
  30. data/lib/sass/logger/log_level.rb +45 -0
  31. data/lib/sass/logger.rb +12 -0
  32. data/lib/sass/media.rb +213 -0
  33. data/lib/sass/plugin/compiler.rb +194 -104
  34. data/lib/sass/plugin/configuration.rb +18 -25
  35. data/lib/sass/plugin/merb.rb +1 -1
  36. data/lib/sass/plugin/staleness_checker.rb +37 -11
  37. data/lib/sass/plugin.rb +10 -13
  38. data/lib/sass/railtie.rb +2 -1
  39. data/lib/sass/repl.rb +5 -6
  40. data/lib/sass/script/css_lexer.rb +8 -4
  41. data/lib/sass/script/css_parser.rb +5 -2
  42. data/lib/sass/script/functions.rb +1547 -618
  43. data/lib/sass/script/lexer.rb +122 -72
  44. data/lib/sass/script/parser.rb +304 -135
  45. data/lib/sass/script/tree/funcall.rb +306 -0
  46. data/lib/sass/script/{interpolation.rb → tree/interpolation.rb} +43 -13
  47. data/lib/sass/script/tree/list_literal.rb +77 -0
  48. data/lib/sass/script/tree/literal.rb +45 -0
  49. data/lib/sass/script/tree/map_literal.rb +64 -0
  50. data/lib/sass/script/{node.rb → tree/node.rb} +30 -12
  51. data/lib/sass/script/{operation.rb → tree/operation.rb} +33 -21
  52. data/lib/sass/script/{string_interpolation.rb → tree/string_interpolation.rb} +14 -4
  53. data/lib/sass/script/{unary_operation.rb → tree/unary_operation.rb} +21 -9
  54. data/lib/sass/script/tree/variable.rb +57 -0
  55. data/lib/sass/script/tree.rb +15 -0
  56. data/lib/sass/script/value/arg_list.rb +36 -0
  57. data/lib/sass/script/value/base.rb +238 -0
  58. data/lib/sass/script/value/bool.rb +40 -0
  59. data/lib/sass/script/{color.rb → value/color.rb} +256 -74
  60. data/lib/sass/script/value/deprecated_false.rb +55 -0
  61. data/lib/sass/script/value/helpers.rb +155 -0
  62. data/lib/sass/script/value/list.rb +128 -0
  63. data/lib/sass/script/value/map.rb +70 -0
  64. data/lib/sass/script/value/null.rb +49 -0
  65. data/lib/sass/script/{number.rb → value/number.rb} +115 -62
  66. data/lib/sass/script/{string.rb → value/string.rb} +9 -11
  67. data/lib/sass/script/value.rb +12 -0
  68. data/lib/sass/script.rb +35 -9
  69. data/lib/sass/scss/css_parser.rb +2 -12
  70. data/lib/sass/scss/parser.rb +657 -230
  71. data/lib/sass/scss/rx.rb +17 -12
  72. data/lib/sass/scss/static_parser.rb +37 -6
  73. data/lib/sass/scss.rb +0 -1
  74. data/lib/sass/selector/abstract_sequence.rb +35 -3
  75. data/lib/sass/selector/comma_sequence.rb +29 -14
  76. data/lib/sass/selector/sequence.rb +371 -74
  77. data/lib/sass/selector/simple.rb +28 -13
  78. data/lib/sass/selector/simple_sequence.rb +163 -36
  79. data/lib/sass/selector.rb +138 -36
  80. data/lib/sass/shared.rb +3 -5
  81. data/lib/sass/source/map.rb +196 -0
  82. data/lib/sass/source/position.rb +39 -0
  83. data/lib/sass/source/range.rb +41 -0
  84. data/lib/sass/stack.rb +126 -0
  85. data/lib/sass/supports.rb +228 -0
  86. data/lib/sass/tree/at_root_node.rb +82 -0
  87. data/lib/sass/tree/comment_node.rb +34 -29
  88. data/lib/sass/tree/content_node.rb +9 -0
  89. data/lib/sass/tree/css_import_node.rb +60 -0
  90. data/lib/sass/tree/debug_node.rb +3 -3
  91. data/lib/sass/tree/directive_node.rb +33 -3
  92. data/lib/sass/tree/each_node.rb +9 -9
  93. data/lib/sass/tree/extend_node.rb +20 -6
  94. data/lib/sass/tree/for_node.rb +6 -6
  95. data/lib/sass/tree/function_node.rb +12 -4
  96. data/lib/sass/tree/if_node.rb +2 -15
  97. data/lib/sass/tree/import_node.rb +11 -5
  98. data/lib/sass/tree/media_node.rb +27 -11
  99. data/lib/sass/tree/mixin_def_node.rb +15 -4
  100. data/lib/sass/tree/mixin_node.rb +27 -7
  101. data/lib/sass/tree/node.rb +69 -35
  102. data/lib/sass/tree/prop_node.rb +47 -31
  103. data/lib/sass/tree/return_node.rb +4 -3
  104. data/lib/sass/tree/root_node.rb +20 -4
  105. data/lib/sass/tree/rule_node.rb +37 -26
  106. data/lib/sass/tree/supports_node.rb +38 -0
  107. data/lib/sass/tree/trace_node.rb +33 -0
  108. data/lib/sass/tree/variable_node.rb +10 -4
  109. data/lib/sass/tree/visitors/base.rb +5 -8
  110. data/lib/sass/tree/visitors/check_nesting.rb +67 -52
  111. data/lib/sass/tree/visitors/convert.rb +134 -53
  112. data/lib/sass/tree/visitors/cssize.rb +245 -51
  113. data/lib/sass/tree/visitors/deep_copy.rb +102 -0
  114. data/lib/sass/tree/visitors/extend.rb +68 -0
  115. data/lib/sass/tree/visitors/perform.rb +331 -105
  116. data/lib/sass/tree/visitors/set_options.rb +125 -0
  117. data/lib/sass/tree/visitors/to_css.rb +259 -95
  118. data/lib/sass/tree/warn_node.rb +3 -3
  119. data/lib/sass/tree/while_node.rb +3 -3
  120. data/lib/sass/util/cross_platform_random.rb +19 -0
  121. data/lib/sass/util/multibyte_string_scanner.rb +157 -0
  122. data/lib/sass/util/normalized_map.rb +130 -0
  123. data/lib/sass/util/ordered_hash.rb +192 -0
  124. data/lib/sass/util/subset_map.rb +11 -2
  125. data/lib/sass/util/test.rb +9 -0
  126. data/lib/sass/util.rb +565 -39
  127. data/lib/sass/version.rb +27 -15
  128. data/lib/sass.rb +39 -4
  129. data/test/sass/cache_test.rb +15 -0
  130. data/test/sass/compiler_test.rb +223 -0
  131. data/test/sass/conversion_test.rb +901 -107
  132. data/test/sass/css2sass_test.rb +94 -0
  133. data/test/sass/engine_test.rb +1059 -164
  134. data/test/sass/exec_test.rb +86 -0
  135. data/test/sass/extend_test.rb +933 -837
  136. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  137. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  138. data/test/sass/functions_test.rb +995 -136
  139. data/test/sass/importer_test.rb +338 -18
  140. data/test/sass/logger_test.rb +58 -0
  141. data/test/sass/more_results/more_import.css +2 -2
  142. data/test/sass/plugin_test.rb +114 -30
  143. data/test/sass/results/cached_import_option.css +3 -0
  144. data/test/sass/results/filename_fn.css +3 -0
  145. data/test/sass/results/import.css +2 -2
  146. data/test/sass/results/import_charset.css +1 -0
  147. data/test/sass/results/import_charset_1_8.css +1 -0
  148. data/test/sass/results/import_charset_ibm866.css +1 -0
  149. data/test/sass/results/import_content.css +1 -0
  150. data/test/sass/results/script.css +1 -1
  151. data/test/sass/results/scss_import.css +2 -2
  152. data/test/sass/results/units.css +2 -2
  153. data/test/sass/script_conversion_test.rb +43 -1
  154. data/test/sass/script_test.rb +380 -36
  155. data/test/sass/scss/css_test.rb +257 -75
  156. data/test/sass/scss/scss_test.rb +2322 -110
  157. data/test/sass/source_map_test.rb +887 -0
  158. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  159. data/test/sass/templates/_double_import_loop2.sass +1 -0
  160. data/test/sass/templates/_filename_fn_import.scss +11 -0
  161. data/test/sass/templates/_imported_content.sass +3 -0
  162. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  163. data/test/sass/templates/bork5.sass +3 -0
  164. data/test/sass/templates/cached_import_option.scss +3 -0
  165. data/test/sass/templates/double_import_loop1.sass +1 -0
  166. data/test/sass/templates/filename_fn.scss +18 -0
  167. data/test/sass/templates/import_charset.sass +2 -0
  168. data/test/sass/templates/import_charset_1_8.sass +2 -0
  169. data/test/sass/templates/import_charset_ibm866.sass +2 -0
  170. data/test/sass/templates/import_content.sass +4 -0
  171. data/test/sass/templates/same_name_different_ext.sass +2 -0
  172. data/test/sass/templates/same_name_different_ext.scss +1 -0
  173. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  174. data/test/sass/templates/single_import_loop.sass +1 -0
  175. data/test/sass/templates/subdir/import_up1.scss +1 -0
  176. data/test/sass/templates/subdir/import_up2.scss +1 -0
  177. data/test/sass/test_helper.rb +1 -1
  178. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  179. data/test/sass/util/normalized_map_test.rb +51 -0
  180. data/test/sass/util_test.rb +183 -0
  181. data/test/sass/value_helpers_test.rb +181 -0
  182. data/test/test_helper.rb +45 -5
  183. data/vendor/listen/CHANGELOG.md +228 -0
  184. data/vendor/listen/CONTRIBUTING.md +38 -0
  185. data/vendor/listen/Gemfile +30 -0
  186. data/vendor/listen/Guardfile +8 -0
  187. data/vendor/{fssm → listen}/LICENSE +1 -1
  188. data/vendor/listen/README.md +315 -0
  189. data/vendor/listen/Rakefile +47 -0
  190. data/vendor/listen/Vagrantfile +96 -0
  191. data/vendor/listen/lib/listen/adapter.rb +214 -0
  192. data/vendor/listen/lib/listen/adapters/bsd.rb +112 -0
  193. data/vendor/listen/lib/listen/adapters/darwin.rb +85 -0
  194. data/vendor/listen/lib/listen/adapters/linux.rb +113 -0
  195. data/vendor/listen/lib/listen/adapters/polling.rb +67 -0
  196. data/vendor/listen/lib/listen/adapters/windows.rb +87 -0
  197. data/vendor/listen/lib/listen/dependency_manager.rb +126 -0
  198. data/vendor/listen/lib/listen/directory_record.rb +371 -0
  199. data/vendor/listen/lib/listen/listener.rb +225 -0
  200. data/vendor/listen/lib/listen/multi_listener.rb +143 -0
  201. data/vendor/listen/lib/listen/turnstile.rb +28 -0
  202. data/vendor/listen/lib/listen/version.rb +3 -0
  203. data/vendor/listen/lib/listen.rb +40 -0
  204. data/vendor/listen/listen.gemspec +22 -0
  205. data/vendor/listen/spec/listen/adapter_spec.rb +183 -0
  206. data/vendor/listen/spec/listen/adapters/bsd_spec.rb +36 -0
  207. data/vendor/listen/spec/listen/adapters/darwin_spec.rb +37 -0
  208. data/vendor/listen/spec/listen/adapters/linux_spec.rb +47 -0
  209. data/vendor/listen/spec/listen/adapters/polling_spec.rb +68 -0
  210. data/vendor/listen/spec/listen/adapters/windows_spec.rb +30 -0
  211. data/vendor/listen/spec/listen/dependency_manager_spec.rb +107 -0
  212. data/vendor/listen/spec/listen/directory_record_spec.rb +1225 -0
  213. data/vendor/listen/spec/listen/listener_spec.rb +169 -0
  214. data/vendor/listen/spec/listen/multi_listener_spec.rb +174 -0
  215. data/vendor/listen/spec/listen/turnstile_spec.rb +56 -0
  216. data/vendor/listen/spec/listen_spec.rb +73 -0
  217. data/vendor/listen/spec/spec_helper.rb +21 -0
  218. data/vendor/listen/spec/support/adapter_helper.rb +629 -0
  219. data/vendor/listen/spec/support/directory_record_helper.rb +55 -0
  220. data/vendor/listen/spec/support/fixtures_helper.rb +29 -0
  221. data/vendor/listen/spec/support/listeners_helper.rb +156 -0
  222. data/vendor/listen/spec/support/platform_helper.rb +15 -0
  223. metadata +344 -271
  224. data/lib/sass/less.rb +0 -382
  225. data/lib/sass/script/bool.rb +0 -18
  226. data/lib/sass/script/funcall.rb +0 -162
  227. data/lib/sass/script/list.rb +0 -76
  228. data/lib/sass/script/literal.rb +0 -245
  229. data/lib/sass/script/variable.rb +0 -54
  230. data/lib/sass/scss/sass_parser.rb +0 -11
  231. data/test/sass/less_conversion_test.rb +0 -653
  232. data/vendor/fssm/README.markdown +0 -55
  233. data/vendor/fssm/Rakefile +0 -59
  234. data/vendor/fssm/VERSION.yml +0 -5
  235. data/vendor/fssm/example.rb +0 -9
  236. data/vendor/fssm/fssm.gemspec +0 -77
  237. data/vendor/fssm/lib/fssm/backends/fsevents.rb +0 -36
  238. data/vendor/fssm/lib/fssm/backends/inotify.rb +0 -26
  239. data/vendor/fssm/lib/fssm/backends/polling.rb +0 -25
  240. data/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +0 -131
  241. data/vendor/fssm/lib/fssm/monitor.rb +0 -26
  242. data/vendor/fssm/lib/fssm/path.rb +0 -91
  243. data/vendor/fssm/lib/fssm/pathname.rb +0 -502
  244. data/vendor/fssm/lib/fssm/state/directory.rb +0 -57
  245. data/vendor/fssm/lib/fssm/state/file.rb +0 -24
  246. data/vendor/fssm/lib/fssm/support.rb +0 -63
  247. data/vendor/fssm/lib/fssm/tree.rb +0 -176
  248. data/vendor/fssm/lib/fssm.rb +0 -33
  249. data/vendor/fssm/profile/prof-cache.rb +0 -40
  250. data/vendor/fssm/profile/prof-fssm-pathname.html +0 -1231
  251. data/vendor/fssm/profile/prof-pathname.rb +0 -68
  252. data/vendor/fssm/profile/prof-plain-pathname.html +0 -988
  253. data/vendor/fssm/profile/prof.html +0 -2379
  254. data/vendor/fssm/spec/path_spec.rb +0 -75
  255. data/vendor/fssm/spec/root/duck/quack.txt +0 -0
  256. data/vendor/fssm/spec/root/file.css +0 -0
  257. data/vendor/fssm/spec/root/file.rb +0 -0
  258. data/vendor/fssm/spec/root/file.yml +0 -0
  259. data/vendor/fssm/spec/root/moo/cow.txt +0 -0
  260. data/vendor/fssm/spec/spec_helper.rb +0 -14
@@ -1,4 +1,3 @@
1
- require 'strscan'
2
1
  require 'set'
3
2
 
4
3
  module Sass
@@ -6,14 +5,30 @@ 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
8
+ # Expose for the SASS parser.
9
+ attr_accessor :offset
10
+
9
11
  # @param str [String, StringScanner] The source document to parse.
10
12
  # Note that `Parser` *won't* raise a nice error message if this isn't properly parsed;
11
13
  # for that, you should use the higher-level {Sass::Engine} or {Sass::CSS}.
12
- # @param line [Fixnum] The line on which the source string appeared,
13
- # if it's part of another document
14
- def initialize(str, line = 1)
14
+ # @param filename [String] The name of the file being parsed. Used for
15
+ # warnings and source maps.
16
+ # @param importer [Sass::Importers::Base] The importer used to import the
17
+ # file being parsed. Used for source maps.
18
+ # @param line [Fixnum] The 1-based line on which the source string appeared,
19
+ # if it's part of another document.
20
+ # @param offset [Fixnum] The 1-based character (not byte) offset in the line on
21
+ # which the source string starts. Used for error reporting and sourcemap
22
+ # building.
23
+ # @comment
24
+ # rubocop:disable ParameterLists
25
+ def initialize(str, filename, importer, line = 1, offset = 1)
26
+ # rubocop:enable ParameterLists
15
27
  @template = str
28
+ @filename = filename
29
+ @importer = importer
16
30
  @line = line
31
+ @offset = offset
17
32
  @strs = []
18
33
  end
19
34
 
@@ -32,28 +47,72 @@ module Sass
32
47
  # Note that this won't assert that the identifier takes up the entire input string;
33
48
  # it's meant to be used with `StringScanner`s as part of other parsers.
34
49
  #
35
- # @return [Array<String, Sass::Script::Node>, nil]
50
+ # @return [Array<String, Sass::Script::Tree::Node>, nil]
36
51
  # The interpolated identifier, or nil if none could be parsed
37
52
  def parse_interp_ident
38
53
  init_scanner!
39
54
  interp_ident
40
55
  end
41
56
 
57
+ # Parses a media query list.
58
+ #
59
+ # @return [Sass::Media::QueryList] The parsed query list
60
+ # @raise [Sass::SyntaxError] if there's a syntax error in the query list,
61
+ # or if it doesn't take up the entire input string.
62
+ def parse_media_query_list
63
+ init_scanner!
64
+ ql = media_query_list
65
+ expected("media query list") unless @scanner.eos?
66
+ ql
67
+ end
68
+
69
+ # Parses an at-root query.
70
+ #
71
+ # @return [Array<String, Sass::Script;:Tree::Node>] The interpolated query.
72
+ # @raise [Sass::SyntaxError] if there's a syntax error in the query,
73
+ # or if it doesn't take up the entire input string.
74
+ def parse_at_root_query
75
+ init_scanner!
76
+ query = at_root_query
77
+ expected("@at-root query list") unless @scanner.eos?
78
+ query
79
+ end
80
+
81
+ # Parses a supports query condition.
82
+ #
83
+ # @return [Sass::Supports::Condition] The parsed condition
84
+ # @raise [Sass::SyntaxError] if there's a syntax error in the condition,
85
+ # or if it doesn't take up the entire input string.
86
+ def parse_supports_condition
87
+ init_scanner!
88
+ condition = supports_condition
89
+ expected("supports condition") unless @scanner.eos?
90
+ condition
91
+ end
92
+
42
93
  private
43
94
 
44
95
  include Sass::SCSS::RX
45
96
 
97
+ def source_position
98
+ Sass::Source::Position.new(@line, @offset)
99
+ end
100
+
101
+ def range(start_pos, end_pos = source_position)
102
+ Sass::Source::Range.new(start_pos, end_pos, @filename, @importer)
103
+ end
104
+
46
105
  def init_scanner!
47
106
  @scanner =
48
107
  if @template.is_a?(StringScanner)
49
108
  @template
50
109
  else
51
- StringScanner.new(@template.gsub("\r", ""))
110
+ Sass::Util::MultibyteStringScanner.new(@template.gsub("\r", ""))
52
111
  end
53
112
  end
54
113
 
55
114
  def stylesheet
56
- node = node(Sass::Tree::RootNode.new(@scanner.string))
115
+ node = node(Sass::Tree::RootNode.new(@scanner.string), source_position)
57
116
  block_contents(node, :stylesheet) {s(node)}
58
117
  end
59
118
 
@@ -87,34 +146,61 @@ module Sass
87
146
  end
88
147
 
89
148
  def process_comment(text, node)
90
- single_line = text =~ /^\/\//
91
- pre_str = single_line ? "" : @scanner.
92
- string[0...@scanner.pos].
93
- reverse[/.*?\*\/(.*?)($|\Z)/, 1].
94
- reverse.gsub(/[^\s]/, ' ')
95
- text = text.sub(/^\s*\/\//, '/*').gsub(/^\s*\/\//, ' *') + ' */' if single_line
96
- comment = Sass::Tree::CommentNode.new(pre_str + text, single_line)
97
- comment.line = @line - text.count("\n")
149
+ silent = text =~ %r{\A//}
150
+ loud = !silent && text =~ %r{\A/[/*]!}
151
+ line = @line - text.count("\n")
152
+
153
+ if silent
154
+ value = [text.sub(%r{\A\s*//}, '/*').gsub(%r{^\s*//}, ' *') + ' */']
155
+ else
156
+ value = Sass::Engine.parse_interp(
157
+ text, line, @scanner.pos - text.size, :filename => @filename)
158
+ value.unshift(@scanner.
159
+ string[0...@scanner.pos].
160
+ reverse[/.*?\*\/(.*?)($|\Z)/, 1].
161
+ reverse.gsub(/[^\s]/, ' '))
162
+ end
163
+
164
+ type = if silent
165
+ :silent
166
+ elsif loud
167
+ :loud
168
+ else
169
+ :normal
170
+ end
171
+ comment = Sass::Tree::CommentNode.new(value, type)
172
+ comment.line = line
98
173
  node << comment
99
174
  end
100
175
 
101
176
  DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
102
- :each, :while, :if, :else, :extend, :import, :media, :charset]
177
+ :each, :while, :if, :else, :extend, :import, :media, :charset, :content,
178
+ :_moz_document, :at_root]
179
+
180
+ PREFIXED_DIRECTIVES = Set[:supports]
103
181
 
104
182
  def directive
183
+ start_pos = source_position
105
184
  return unless tok(/@/)
106
185
  name = tok!(IDENT)
107
186
  ss
108
187
 
109
- if dir = special_directive(name)
188
+ if (dir = special_directive(name, start_pos))
189
+ return dir
190
+ elsif (dir = prefixed_directive(name, start_pos))
110
191
  return dir
111
192
  end
112
193
 
113
194
  # Most at-rules take expressions (e.g. @import),
114
- # but some (e.g. @page) take selector-like arguments
115
- val = str {break unless expr}
116
- val ||= CssParser.new(@scanner, @line).parse_selector_string
117
- node = node(Sass::Tree::DirectiveNode.new("@#{name} #{val}".strip))
195
+ # but some (e.g. @page) take selector-like arguments.
196
+ # Some take no arguments at all.
197
+ val = expr || selector
198
+ val = val ? ["@#{name} "] + Sass::Util.strip_string_array(val) : ["@#{name}"]
199
+ directive_body(val, start_pos)
200
+ end
201
+
202
+ def directive_body(value, start_pos)
203
+ node = Sass::Tree::DirectiveNode.new(value)
118
204
 
119
205
  if tok(/\{/)
120
206
  node.has_children = true
@@ -122,48 +208,65 @@ module Sass
122
208
  tok!(/\}/)
123
209
  end
124
210
 
125
- node
211
+ node(node, start_pos)
126
212
  end
127
213
 
128
- def special_directive(name)
214
+ def special_directive(name, start_pos)
129
215
  sym = name.gsub('-', '_').to_sym
130
- DIRECTIVES.include?(sym) && send("#{sym}_directive")
216
+ DIRECTIVES.include?(sym) && send("#{sym}_directive", start_pos)
217
+ end
218
+
219
+ def prefixed_directive(name, start_pos)
220
+ sym = name.gsub(/^-[a-z0-9]+-/i, '').gsub('-', '_').to_sym
221
+ PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name, start_pos)
131
222
  end
132
223
 
133
- def mixin_directive
224
+ def mixin_directive(start_pos)
134
225
  name = tok! IDENT
135
- args = sass_script(:parse_mixin_definition_arglist)
226
+ args, splat = sass_script(:parse_mixin_definition_arglist)
136
227
  ss
137
- block(node(Sass::Tree::MixinDefNode.new(name, args)), :directive)
228
+ block(node(Sass::Tree::MixinDefNode.new(name, args, splat), start_pos), :directive)
138
229
  end
139
230
 
140
- def include_directive
231
+ def include_directive(start_pos)
141
232
  name = tok! IDENT
142
- args, keywords = sass_script(:parse_mixin_include_arglist)
233
+ args, keywords, splat, kwarg_splat = sass_script(:parse_mixin_include_arglist)
234
+ ss
235
+ include_node = node(
236
+ Sass::Tree::MixinNode.new(name, args, keywords, splat, kwarg_splat), start_pos)
237
+ if tok?(/\{/)
238
+ include_node.has_children = true
239
+ block(include_node, :directive)
240
+ else
241
+ include_node
242
+ end
243
+ end
244
+
245
+ def content_directive(start_pos)
143
246
  ss
144
- node(Sass::Tree::MixinNode.new(name, args, keywords))
247
+ node(Sass::Tree::ContentNode.new, start_pos)
145
248
  end
146
249
 
147
- def function_directive
250
+ def function_directive(start_pos)
148
251
  name = tok! IDENT
149
- args = sass_script(:parse_function_definition_arglist)
252
+ args, splat = sass_script(:parse_function_definition_arglist)
150
253
  ss
151
- block(node(Sass::Tree::FunctionNode.new(name, args)), :function)
254
+ block(node(Sass::Tree::FunctionNode.new(name, args, splat), start_pos), :function)
152
255
  end
153
256
 
154
- def return_directive
155
- node(Sass::Tree::ReturnNode.new(sass_script(:parse)))
257
+ def return_directive(start_pos)
258
+ node(Sass::Tree::ReturnNode.new(sass_script(:parse)), start_pos)
156
259
  end
157
260
 
158
- def debug_directive
159
- node(Sass::Tree::DebugNode.new(sass_script(:parse)))
261
+ def debug_directive(start_pos)
262
+ node(Sass::Tree::DebugNode.new(sass_script(:parse)), start_pos)
160
263
  end
161
264
 
162
- def warn_directive
163
- node(Sass::Tree::WarnNode.new(sass_script(:parse)))
265
+ def warn_directive(start_pos)
266
+ node(Sass::Tree::WarnNode.new(sass_script(:parse)), start_pos)
164
267
  end
165
268
 
166
- def for_directive
269
+ def for_directive(start_pos)
167
270
  tok!(/\$/)
168
271
  var = tok! IDENT
169
272
  ss
@@ -177,31 +280,37 @@ module Sass
177
280
  to = sass_script(:parse)
178
281
  ss
179
282
 
180
- block(node(Sass::Tree::ForNode.new(var, from, to, exclusive)), :directive)
283
+ block(node(Sass::Tree::ForNode.new(var, from, to, exclusive), start_pos), :directive)
181
284
  end
182
285
 
183
- def each_directive
286
+ def each_directive(start_pos)
184
287
  tok!(/\$/)
185
- var = tok! IDENT
288
+ vars = [tok!(IDENT)]
186
289
  ss
290
+ while tok(/,/)
291
+ ss
292
+ tok!(/\$/)
293
+ vars << tok!(IDENT)
294
+ ss
295
+ end
187
296
 
188
297
  tok!(/in/)
189
298
  list = sass_script(:parse)
190
299
  ss
191
300
 
192
- block(node(Sass::Tree::EachNode.new(var, list)), :directive)
301
+ block(node(Sass::Tree::EachNode.new(vars, list), start_pos), :directive)
193
302
  end
194
303
 
195
- def while_directive
304
+ def while_directive(start_pos)
196
305
  expr = sass_script(:parse)
197
306
  ss
198
- block(node(Sass::Tree::WhileNode.new(expr)), :directive)
307
+ block(node(Sass::Tree::WhileNode.new(expr), start_pos), :directive)
199
308
  end
200
309
 
201
- def if_directive
310
+ def if_directive(start_pos)
202
311
  expr = sass_script(:parse)
203
312
  ss
204
- node = block(node(Sass::Tree::IfNode.new(expr)), :directive)
313
+ node = block(node(Sass::Tree::IfNode.new(expr), start_pos), :directive)
205
314
  pos = @scanner.pos
206
315
  line = @line
207
316
  ss
@@ -216,10 +325,11 @@ module Sass
216
325
  end
217
326
 
218
327
  def else_block(node)
328
+ start_pos = source_position
219
329
  return unless tok(/@else/)
220
330
  ss
221
331
  else_node = block(
222
- Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))),
332
+ node(Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))), start_pos),
223
333
  :directive)
224
334
  node.add_else(else_node)
225
335
  pos = @scanner.pos
@@ -235,107 +345,273 @@ module Sass
235
345
  end
236
346
  end
237
347
 
238
- def else_directive
348
+ def else_directive(start_pos)
239
349
  err("Invalid CSS: @else must come after @if")
240
350
  end
241
351
 
242
- def extend_directive
243
- node(Sass::Tree::ExtendNode.new(expr!(:selector)))
352
+ def extend_directive(start_pos)
353
+ selector, selector_range = expr!(:selector_sequence)
354
+ optional = tok(OPTIONAL)
355
+ ss
356
+ node(Sass::Tree::ExtendNode.new(selector, !!optional, selector_range), start_pos)
244
357
  end
245
358
 
246
- def import_directive
359
+ def import_directive(start_pos)
247
360
  values = []
248
361
 
249
362
  loop do
250
363
  values << expr!(:import_arg)
251
- break if use_css_import? || !tok(/,\s*/)
364
+ break if use_css_import?
365
+ break unless tok(/,/)
366
+ ss
252
367
  end
253
368
 
254
- return values
369
+ values
255
370
  end
256
371
 
257
372
  def import_arg
258
- return unless arg = tok(STRING) || (uri = tok!(URI))
259
- path = @scanner[1] || @scanner[2] || @scanner[3]
260
- ss
373
+ start_pos = source_position
374
+ return unless (str = tok(STRING)) || (uri = tok?(/url\(/i))
375
+ if uri
376
+ str = sass_script(:parse_string)
377
+ ss
378
+ media = media_query_list
379
+ ss
380
+ return node(Tree::CssImportNode.new(str, media.to_a), start_pos)
381
+ end
261
382
 
262
- media = str {media_query_list}.strip
383
+ path = @scanner[1] || @scanner[2]
384
+ ss
263
385
 
264
- if uri || path =~ /^http:\/\// || !media.strip.empty? || use_css_import?
265
- return node(Sass::Tree::DirectiveNode.new("@import #{arg} #{media}".strip))
386
+ media = media_query_list
387
+ if path =~ %r{^(https?:)?//} || media || use_css_import?
388
+ return node(Sass::Tree::CssImportNode.new(str, media.to_a), start_pos)
266
389
  end
267
390
 
268
- node(Sass::Tree::ImportNode.new(path.strip))
391
+ node(Sass::Tree::ImportNode.new(path.strip), start_pos)
269
392
  end
270
393
 
271
394
  def use_css_import?; false; end
272
395
 
273
- def media_directive
274
- val = str {media_query_list}.strip
275
- block(node(Sass::Tree::MediaNode.new(val)), :directive)
396
+ def media_directive(start_pos)
397
+ block(node(Sass::Tree::MediaNode.new(expr!(:media_query_list).to_a), start_pos), :directive)
276
398
  end
277
399
 
278
400
  # http://www.w3.org/TR/css3-mediaqueries/#syntax
279
401
  def media_query_list
280
- return unless media_query
402
+ query = media_query
403
+ return unless query
404
+ queries = [query]
281
405
 
282
406
  ss
283
407
  while tok(/,/)
284
- ss; expr!(:media_query); ss
408
+ ss; queries << expr!(:media_query)
285
409
  end
410
+ ss
286
411
 
287
- true
412
+ Sass::Media::QueryList.new(queries)
288
413
  end
289
414
 
290
415
  def media_query
291
- if tok(/only|not/i)
416
+ if (ident1 = interp_ident)
292
417
  ss
293
- @expected = "media type (e.g. print, screen)"
294
- tok!(IDENT)
418
+ ident2 = interp_ident
295
419
  ss
296
- elsif !tok(IDENT) && !media_expr
297
- return
420
+ if ident2 && ident2.length == 1 && ident2[0].is_a?(String) && ident2[0].downcase == 'and'
421
+ query = Sass::Media::Query.new([], ident1, [])
422
+ else
423
+ if ident2
424
+ query = Sass::Media::Query.new(ident1, ident2, [])
425
+ else
426
+ query = Sass::Media::Query.new([], ident1, [])
427
+ end
428
+ return query unless tok(/and/i)
429
+ ss
430
+ end
298
431
  end
299
432
 
433
+ if query
434
+ expr = expr!(:media_expr)
435
+ else
436
+ expr = media_expr
437
+ return unless expr
438
+ end
439
+ query ||= Sass::Media::Query.new([], [], [])
440
+ query.expressions << expr
441
+
300
442
  ss
301
443
  while tok(/and/i)
302
- ss; expr!(:media_expr); ss
444
+ ss; query.expressions << expr!(:media_expr)
303
445
  end
304
446
 
305
- true
447
+ query
306
448
  end
307
449
 
308
- def media_expr
450
+ def query_expr
451
+ interp = interpolation
452
+ return interp if interp
309
453
  return unless tok(/\(/)
454
+ res = ['(']
310
455
  ss
311
- @expected = "media feature (e.g. min-device-width, color)"
312
- tok!(IDENT)
313
- ss
456
+ res << sass_script(:parse)
314
457
 
315
458
  if tok(/:/)
316
- ss; expr!(:expr)
459
+ res << ': '
460
+ ss
461
+ res << sass_script(:parse)
317
462
  end
318
- tok!(/\)/)
463
+ res << tok!(/\)/)
319
464
  ss
320
-
321
- true
465
+ res
322
466
  end
323
467
 
324
- def charset_directive
468
+ # Aliases allow us to use different descriptions if the same
469
+ # expression fails in different contexts.
470
+ alias_method :media_expr, :query_expr
471
+ alias_method :at_root_query, :query_expr
472
+
473
+ def charset_directive(start_pos)
325
474
  tok! STRING
326
475
  name = @scanner[1] || @scanner[2]
327
476
  ss
328
- node(Sass::Tree::CharsetNode.new(name))
477
+ node(Sass::Tree::CharsetNode.new(name), start_pos)
478
+ end
479
+
480
+ # The document directive is specified in
481
+ # http://www.w3.org/TR/css3-conditional/, but Gecko allows the
482
+ # `url-prefix` and `domain` functions to omit quotation marks, contrary to
483
+ # the standard.
484
+ #
485
+ # We could parse all document directives according to Mozilla's syntax,
486
+ # but if someone's using e.g. @-webkit-document we don't want them to
487
+ # think WebKit works sans quotes.
488
+ def _moz_document_directive(start_pos)
489
+ res = ["@-moz-document "]
490
+ loop do
491
+ res << str {ss} << expr!(:moz_document_function)
492
+ if (c = tok(/,/))
493
+ res << c
494
+ else
495
+ break
496
+ end
497
+ end
498
+ directive_body(res.flatten, start_pos)
499
+ end
500
+
501
+ def moz_document_function
502
+ val = interp_uri || _interp_string(:url_prefix) ||
503
+ _interp_string(:domain) || function(!:allow_var) || interpolation
504
+ return unless val
505
+ ss
506
+ val
507
+ end
508
+
509
+ def at_root_directive(start_pos)
510
+ if tok?(/\(/) && (expr = at_root_query)
511
+ return block(node(Sass::Tree::AtRootNode.new(expr), start_pos), :directive)
512
+ end
513
+
514
+ at_root_node = node(Sass::Tree::AtRootNode.new, start_pos)
515
+ rule_node = ruleset
516
+ return block(at_root_node, :stylesheet) unless rule_node
517
+ at_root_node << rule_node
518
+ at_root_node
519
+ end
520
+
521
+ def at_root_directive_list
522
+ return unless (first = tok(IDENT))
523
+ arr = [first]
524
+ ss
525
+ while (e = tok(IDENT))
526
+ arr << e
527
+ ss
528
+ end
529
+ arr
530
+ end
531
+
532
+ # http://www.w3.org/TR/css3-conditional/
533
+ def supports_directive(name, start_pos)
534
+ condition = expr!(:supports_condition)
535
+ node = Sass::Tree::SupportsNode.new(name, condition)
536
+
537
+ tok!(/\{/)
538
+ node.has_children = true
539
+ block_contents(node, :directive)
540
+ tok!(/\}/)
541
+
542
+ node(node, start_pos)
543
+ end
544
+
545
+ def supports_condition
546
+ supports_negation || supports_operator || supports_interpolation
547
+ end
548
+
549
+ def supports_negation
550
+ return unless tok(/not/i)
551
+ ss
552
+ Sass::Supports::Negation.new(expr!(:supports_condition_in_parens))
553
+ end
554
+
555
+ def supports_operator
556
+ cond = supports_condition_in_parens
557
+ return unless cond
558
+ while (op = tok(/and|or/i))
559
+ ss
560
+ cond = Sass::Supports::Operator.new(
561
+ cond, expr!(:supports_condition_in_parens), op)
562
+ end
563
+ cond
564
+ end
565
+
566
+ def supports_condition_in_parens
567
+ interp = supports_interpolation
568
+ return interp if interp
569
+ return unless tok(/\(/); ss
570
+ if (cond = supports_condition)
571
+ tok!(/\)/); ss
572
+ cond
573
+ else
574
+ name = sass_script(:parse)
575
+ tok!(/:/); ss
576
+ value = sass_script(:parse)
577
+ tok!(/\)/); ss
578
+ Sass::Supports::Declaration.new(name, value)
579
+ end
580
+ end
581
+
582
+ def supports_declaration_condition
583
+ return unless tok(/\(/); ss
584
+ supports_declaration_body
585
+ end
586
+
587
+ def supports_interpolation
588
+ interp = interpolation
589
+ return unless interp
590
+ ss
591
+ Sass::Supports::Interpolation.new(interp)
329
592
  end
330
593
 
331
594
  def variable
332
595
  return unless tok(/\$/)
596
+ start_pos = source_position
333
597
  name = tok!(IDENT)
334
598
  ss; tok!(/:/); ss
335
599
 
336
600
  expr = sass_script(:parse)
337
- guarded = tok(DEFAULT)
338
- node(Sass::Tree::VariableNode.new(name, expr, guarded))
601
+ while tok(/!/)
602
+ flag_name = tok!(IDENT)
603
+ if flag_name == 'default'
604
+ guarded ||= true
605
+ elsif flag_name == 'global'
606
+ global ||= true
607
+ else
608
+ raise Sass::SyntaxError.new("Invalid flag \"!#{flag_name}\".", :line => @line)
609
+ end
610
+ ss
611
+ end
612
+
613
+ result = Sass::Tree::VariableNode.new(name, expr, guarded, global)
614
+ node(result, start_pos)
339
615
  end
340
616
 
341
617
  def operator
@@ -346,13 +622,12 @@ module Sass
346
622
  str {ss if tok(/[\/,:.=]/)}
347
623
  end
348
624
 
349
- def unary_operator
350
- tok(/[+-]/)
351
- end
352
-
353
625
  def ruleset
354
- return unless rules = selector_sequence
355
- block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)), :ruleset)
626
+ start_pos = source_position
627
+ rules, source_range = selector_sequence
628
+ return unless rules
629
+ block(node(
630
+ Sass::Tree::RuleNode.new(rules.flatten.compact, source_range), start_pos), :ruleset)
356
631
  end
357
632
 
358
633
  def block(node, context)
@@ -383,11 +658,11 @@ module Sass
383
658
  def has_children?(child_or_array)
384
659
  return false unless child_or_array
385
660
  return child_or_array.last.has_children if child_or_array.is_a?(Array)
386
- return child_or_array.has_children
661
+ child_or_array.has_children
387
662
  end
388
663
 
389
664
  # This is a nasty hack, and the only place in the parser
390
- # that requires backtracking.
665
+ # that requires a large amount of backtracking.
391
666
  # The reason is that we can't figure out if certain strings
392
667
  # are declarations or rulesets with fixed finite lookahead.
393
668
  # For example, "foo:bar baz baz baz..." could be either a property
@@ -421,40 +696,46 @@ module Sass
421
696
  end
422
697
 
423
698
  def selector_sequence
424
- if sel = tok(STATIC_SELECTOR)
425
- return [sel]
699
+ start_pos = source_position
700
+ if (sel = tok(STATIC_SELECTOR, true))
701
+ return [sel], range(start_pos)
426
702
  end
427
703
 
428
704
  rules = []
429
- return unless v = selector
705
+ v = selector
706
+ return unless v
430
707
  rules.concat v
431
708
 
432
709
  ws = ''
433
710
  while tok(/,/)
434
711
  ws << str {ss}
435
- if v = selector
712
+ if (v = selector)
436
713
  rules << ',' << ws
437
714
  rules.concat v
438
715
  ws = ''
439
716
  end
440
717
  end
441
- rules
718
+ return rules, range(start_pos)
442
719
  end
443
720
 
444
721
  def selector
445
- return unless sel = _selector
722
+ sel = _selector
723
+ return unless sel
446
724
  sel.to_a
447
725
  end
448
726
 
449
727
  def selector_comma_sequence
450
- return unless sel = _selector
728
+ sel = _selector
729
+ return unless sel
451
730
  selectors = [sel]
452
731
  ws = ''
453
732
  while tok(/,/)
454
- ws << str{ss}
455
- if sel = _selector
733
+ ws << str {ss}
734
+ if (sel = _selector)
456
735
  selectors << sel
457
- selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members) if ws.include?("\n")
736
+ if ws.include?("\n")
737
+ selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members)
738
+ end
458
739
  ws = ''
459
740
  end
460
741
  end
@@ -463,54 +744,75 @@ module Sass
463
744
 
464
745
  def _selector
465
746
  # The combinator here allows the "> E" hack
466
- return unless val = combinator || simple_selector_sequence
467
- nl = str{ss}.include?("\n")
747
+ val = combinator || simple_selector_sequence
748
+ return unless val
749
+ nl = str {ss}.include?("\n")
468
750
  res = []
469
751
  res << val
470
752
  res << "\n" if nl
471
753
 
472
- while val = combinator || simple_selector_sequence
754
+ while (val = combinator || simple_selector_sequence)
473
755
  res << val
474
- res << "\n" if str{ss}.include?("\n")
756
+ res << "\n" if str {ss}.include?("\n")
475
757
  end
476
758
  Selector::Sequence.new(res.compact)
477
759
  end
478
760
 
479
761
  def combinator
480
- tok(PLUS) || tok(GREATER) || tok(TILDE)
762
+ tok(PLUS) || tok(GREATER) || tok(TILDE) || reference_combinator
763
+ end
764
+
765
+ def reference_combinator
766
+ return unless tok(/\//)
767
+ res = ['/']
768
+ ns, name = expr!(:qualified_name)
769
+ res << ns << '|' if ns
770
+ res << name << tok!(/\//)
771
+ res = res.flatten
772
+ res = res.join '' if res.all? {|e| e.is_a?(String)}
773
+ res
481
774
  end
482
775
 
483
776
  def simple_selector_sequence
484
- # This allows for stuff like http://www.w3.org/TR/css3-animations/#keyframes-
485
- return expr unless e = element_name || id_selector || class_selector ||
486
- attrib || negation || pseudo || parent_selector || interpolation_selector
777
+ # Returning expr by default allows for stuff like
778
+ # http://www.w3.org/TR/css3-animations/#keyframes-
779
+
780
+ start_pos = source_position
781
+ e = element_name || id_selector ||
782
+ class_selector || placeholder_selector || attrib || pseudo ||
783
+ parent_selector || interpolation_selector
784
+ return expr(!:allow_var) unless e
487
785
  res = [e]
488
786
 
489
787
  # The tok(/\*/) allows the "E*" hack
490
- while v = element_name || id_selector || class_selector ||
491
- attrib || negation || pseudo || interpolation_selector ||
492
- (tok(/\*/) && Selector::Universal.new(nil))
788
+ while (v = id_selector || class_selector || placeholder_selector ||
789
+ attrib || pseudo || interpolation_selector ||
790
+ (tok(/\*/) && Selector::Universal.new(nil)))
493
791
  res << v
494
792
  end
495
793
 
496
- if tok?(/&/)
794
+ pos = @scanner.pos
795
+ line = @line
796
+ if (sel = str? {simple_selector_sequence})
797
+ @scanner.pos = pos
798
+ @line = line
497
799
  begin
498
- expected('"{"')
800
+ # If we see "*E", don't force a throw because this could be the
801
+ # "*prop: val" hack.
802
+ expected('"{"') if res.length == 1 && res[0].is_a?(Selector::Universal)
803
+ throw_error {expected('"{"')}
499
804
  rescue Sass::SyntaxError => e
500
- e.message << "\n\n" << <<MESSAGE
501
- In Sass 3, the parent selector & can only be used where element names are valid,
502
- since it could potentially be replaced by an element name.
503
- MESSAGE
805
+ e.message << "\n\n\"#{sel}\" may only be used at the beginning of a compound selector."
504
806
  raise e
505
807
  end
506
808
  end
507
809
 
508
- Selector::SimpleSequence.new(res)
810
+ Selector::SimpleSequence.new(res, tok(/!/), range(start_pos))
509
811
  end
510
812
 
511
813
  def parent_selector
512
814
  return unless tok(/&/)
513
- Selector::Parent.new
815
+ Selector::Parent.new(interp_ident(NAME) || [])
514
816
  end
515
817
 
516
818
  def class_selector
@@ -525,13 +827,15 @@ MESSAGE
525
827
  Selector::Id.new(merge(expr!(:interp_name)))
526
828
  end
527
829
 
830
+ def placeholder_selector
831
+ return unless tok(/%/)
832
+ @expected = "placeholder name"
833
+ Selector::Placeholder.new(merge(expr!(:interp_ident)))
834
+ end
835
+
528
836
  def element_name
529
- return unless name = interp_ident || tok(/\*/) || (tok?(/\|/) && "")
530
- if tok(/\|/)
531
- @expected = "element name or *"
532
- ns = name
533
- name = interp_ident || tok!(/\*/)
534
- end
837
+ ns, name = Sass::Util.destructure(qualified_name(:allow_star_name))
838
+ return unless ns || name
535
839
 
536
840
  if name == '*'
537
841
  Selector::Universal.new(merge(ns))
@@ -540,9 +844,20 @@ MESSAGE
540
844
  end
541
845
  end
542
846
 
847
+ def qualified_name(allow_star_name = false)
848
+ name = interp_ident || tok(/\*/) || (tok?(/\|/) && "")
849
+ return unless name
850
+ return nil, name unless tok(/\|/)
851
+
852
+ return name, expr!(:interp_ident) unless allow_star_name
853
+ @expected = "identifier or *"
854
+ return name, interp_ident || tok!(/\*/)
855
+ end
856
+
543
857
  def interpolation_selector
544
- return unless script = interpolation
545
- Selector::Interpolation.new(script)
858
+ if (script = interpolation)
859
+ Selector::Interpolation.new(script)
860
+ end
546
861
  end
547
862
 
548
863
  def attrib
@@ -551,28 +866,26 @@ MESSAGE
551
866
  ns, name = attrib_name!
552
867
  ss
553
868
 
554
- if op = tok(/=/) ||
555
- tok(INCLUDES) ||
556
- tok(DASHMATCH) ||
557
- tok(PREFIXMATCH) ||
558
- tok(SUFFIXMATCH) ||
559
- tok(SUBSTRINGMATCH)
869
+ op = tok(/=/) ||
870
+ tok(INCLUDES) ||
871
+ tok(DASHMATCH) ||
872
+ tok(PREFIXMATCH) ||
873
+ tok(SUFFIXMATCH) ||
874
+ tok(SUBSTRINGMATCH)
875
+ if op
560
876
  @expected = "identifier or string"
561
877
  ss
562
- if val = tok(IDENT)
563
- val = [val]
564
- else
565
- val = expr!(:interp_string)
566
- end
878
+ val = interp_ident || expr!(:interp_string)
567
879
  ss
568
880
  end
569
- tok(/\]/)
881
+ flags = interp_ident || interp_string
882
+ tok!(/\]/)
570
883
 
571
- Selector::Attribute.new(merge(name), merge(ns), op, merge(val))
884
+ Selector::Attribute.new(merge(name), merge(ns), op, merge(val), merge(flags))
572
885
  end
573
886
 
574
887
  def attrib_name!
575
- if name_or_ns = interp_ident
888
+ if (name_or_ns = interp_ident)
576
889
  # E, E|E
577
890
  if tok(/\|(?!=)/)
578
891
  ns = name_or_ns
@@ -590,59 +903,89 @@ MESSAGE
590
903
  end
591
904
 
592
905
  def pseudo
593
- return unless s = tok(/::?/)
906
+ s = tok(/::?/)
907
+ return unless s
594
908
  @expected = "pseudoclass or pseudoelement"
595
909
  name = expr!(:interp_ident)
596
910
  if tok(/\(/)
597
911
  ss
598
- arg = expr!(:pseudo_expr)
912
+ arg = expr!(:pseudo_arg)
913
+ while tok(/,/)
914
+ arg << ',' << str {ss}
915
+ arg.concat expr!(:pseudo_arg)
916
+ end
599
917
  tok!(/\)/)
600
918
  end
601
919
  Selector::Pseudo.new(s == ':' ? :class : :element, merge(name), merge(arg))
602
920
  end
603
921
 
604
- def pseudo_expr
605
- return unless e = tok(PLUS) || tok(/-/) || tok(NUMBER) ||
606
- interp_string || tok(IDENT) || interpolation
607
- res = [e, str{ss}]
608
- while e = tok(PLUS) || tok(/-/) || tok(NUMBER) ||
609
- interp_string || tok(IDENT) || interpolation
610
- res << e << str{ss}
922
+ def pseudo_arg
923
+ # In the CSS spec, every pseudo-class/element either takes a pseudo
924
+ # expression or a selector comma sequence as an argument. However, we
925
+ # don't want to have to know which takes which, so we handle both at
926
+ # once.
927
+ #
928
+ # However, there are some ambiguities between the two. For instance, "n"
929
+ # could start a pseudo expression like "n+1", or it could start a
930
+ # selector like "n|m". In order to handle this, we must regrettably
931
+ # backtrack.
932
+ expr, sel = nil, nil
933
+ pseudo_err = catch_error do
934
+ expr = pseudo_expr
935
+ next if tok?(/[,)]/)
936
+ expr = nil
937
+ expected '")"'
611
938
  end
612
- res
939
+
940
+ return expr if expr
941
+ sel_err = catch_error {sel = selector}
942
+ return sel if sel
943
+ rethrow pseudo_err if pseudo_err
944
+ rethrow sel_err if sel_err
945
+ nil
613
946
  end
614
947
 
615
- def negation
616
- return unless name = tok(NOT) || tok(MOZ_ANY)
617
- ss
618
- @expected = "selector"
619
- sel = selector_comma_sequence
620
- tok!(/\)/)
621
- Selector::SelectorPseudoClass.new(name[1...-1], sel)
948
+ def pseudo_expr_token
949
+ tok(PLUS) || tok(/[-*]/) || tok(NUMBER) || interp_string || tok(IDENT) || interpolation
950
+ end
951
+
952
+ def pseudo_expr
953
+ e = pseudo_expr_token
954
+ return unless e
955
+ res = [e, str {ss}]
956
+ while (e = pseudo_expr_token)
957
+ res << e << str {ss}
958
+ end
959
+ res
622
960
  end
623
961
 
624
962
  def declaration
625
963
  # This allows the "*prop: val", ":prop: val", and ".prop: val" hacks
626
- if s = tok(/[:\*\.]|\#(?!\{)/)
964
+ name_start_pos = source_position
965
+ if (s = tok(/[:\*\.]|\#(?!\{)/))
627
966
  @use_property_exception = s !~ /[\.\#]/
628
- name = [s, str{ss}, *expr!(:interp_ident)]
967
+ name = [s, str {ss}, *expr!(:interp_ident)]
629
968
  else
630
- return unless name = interp_ident
969
+ name = interp_ident
970
+ return unless name
631
971
  name = [name] if name.is_a?(String)
632
972
  end
633
- if comment = tok(COMMENT)
973
+ if (comment = tok(COMMENT))
634
974
  name << comment
635
975
  end
976
+ name_end_pos = source_position
636
977
  ss
637
978
 
638
979
  tok!(/:/)
639
- space, value = value!
640
- ss
641
- important = tok(IMPORTANT)
980
+ value_start_pos, space, value = value!
981
+ value_end_pos = source_position
642
982
  ss
643
983
  require_block = tok?(/\{/)
644
984
 
645
- node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, !!important, :new))
985
+ node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new),
986
+ name_start_pos, value_end_pos)
987
+ node.name_source_range = range(name_start_pos, name_end_pos)
988
+ node.value_source_range = range(value_start_pos, value_end_pos)
646
989
 
647
990
  return node unless require_block
648
991
  nested_properties! node, space
@@ -650,29 +993,29 @@ MESSAGE
650
993
 
651
994
  def value!
652
995
  space = !str {ss}.empty?
996
+ value_start_pos = source_position
653
997
  @use_property_exception ||= space || !tok?(IDENT)
654
998
 
655
- return true, Sass::Script::String.new("") if tok?(/\{/)
999
+ if tok?(/\{/)
1000
+ str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(""))
1001
+ str.line = source_position.line
1002
+ str.source_range = range(source_position)
1003
+ return value_start_pos, true, str
1004
+ end
1005
+
1006
+ start_pos = source_position
656
1007
  # This is a bit of a dirty trick:
657
1008
  # if the value is completely static,
658
1009
  # we don't parse it at all, and instead return a plain old string
659
1010
  # containing the value.
660
1011
  # This results in a dramatic speed increase.
661
- if val = tok(STATIC_VALUE)
662
- return space, Sass::Script::String.new(val.strip)
1012
+ if (val = tok(STATIC_VALUE, true))
1013
+ str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(val.strip))
1014
+ str.line = start_pos.line
1015
+ str.source_range = range(start_pos)
1016
+ return value_start_pos, space, str
663
1017
  end
664
- return space, sass_script(:parse)
665
- end
666
-
667
- def plain_value
668
- return unless tok(/:/)
669
- space = !str {ss}.empty?
670
- @use_property_exception ||= space || !tok?(IDENT)
671
-
672
- expression = expr
673
- expression << tok(IMPORTANT) if expression
674
- # expression, space, value
675
- return expression, space, expression || [""]
1018
+ return value_start_pos, space, sass_script(:parse)
676
1019
  end
677
1020
 
678
1021
  def nested_properties!(node, space)
@@ -686,43 +1029,55 @@ MESSAGE
686
1029
  block(node, :property)
687
1030
  end
688
1031
 
689
- def expr
690
- return unless t = term
691
- res = [t, str{ss}]
1032
+ def expr(allow_var = true)
1033
+ t = term(allow_var)
1034
+ return unless t
1035
+ res = [t, str {ss}]
692
1036
 
693
- while (o = operator) && (t = term)
694
- res << o << t << str{ss}
1037
+ while (o = operator) && (t = term(allow_var))
1038
+ res << o << t << str {ss}
695
1039
  end
696
1040
 
697
- res
1041
+ res.flatten
698
1042
  end
699
1043
 
700
- def term
701
- unless e = tok(NUMBER) ||
702
- tok(URI) ||
703
- function ||
704
- tok(STRING) ||
1044
+ def term(allow_var)
1045
+ e = tok(NUMBER) ||
1046
+ interp_uri ||
1047
+ function(allow_var) ||
1048
+ interp_string ||
705
1049
  tok(UNICODERANGE) ||
706
- tok(IDENT) ||
707
- tok(HEXCOLOR)
1050
+ interp_ident ||
1051
+ tok(HEXCOLOR) ||
1052
+ (allow_var && var_expr)
1053
+ return e if e
708
1054
 
709
- return unless op = unary_operator
710
- @expected = "number or function"
711
- return [op, tok(NUMBER) || expr!(:function)]
712
- end
713
- e
1055
+ op = tok(/[+-]/)
1056
+ return unless op
1057
+ @expected = "number or function"
1058
+ [op,
1059
+ tok(NUMBER) || function(allow_var) || (allow_var && var_expr) || expr!(:interpolation)]
714
1060
  end
715
1061
 
716
- def function
717
- return unless name = tok(FUNCTION)
1062
+ def function(allow_var)
1063
+ name = tok(FUNCTION)
1064
+ return unless name
718
1065
  if name == "expression(" || name == "calc("
719
1066
  str, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
720
1067
  [name, str]
721
1068
  else
722
- [name, str{ss}, expr, tok!(/\)/)]
1069
+ [name, str {ss}, expr(allow_var), tok!(/\)/)]
723
1070
  end
724
1071
  end
725
1072
 
1073
+ def var_expr
1074
+ return unless tok(/\$/)
1075
+ line = @line
1076
+ var = Sass::Script::Tree::Variable.new(tok!(IDENT))
1077
+ var.line = line
1078
+ var
1079
+ end
1080
+
726
1081
  def interpolation
727
1082
  return unless tok(INTERP_START)
728
1083
  sass_script(:parse_interpolated)
@@ -732,11 +1087,16 @@ MESSAGE
732
1087
  _interp_string(:double) || _interp_string(:single)
733
1088
  end
734
1089
 
1090
+ def interp_uri
1091
+ _interp_string(:uri)
1092
+ end
1093
+
735
1094
  def _interp_string(type)
736
- return unless start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, false]])
1095
+ start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][false])
1096
+ return unless start
737
1097
  res = [start]
738
1098
 
739
- mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, true]]
1099
+ mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][true]
740
1100
  # @scanner[2].empty? means we've started an interpolated section
741
1101
  while @scanner[2] == '#{'
742
1102
  @scanner.pos -= 2 # Don't consume the #{
@@ -747,14 +1107,22 @@ MESSAGE
747
1107
  end
748
1108
 
749
1109
  def interp_ident(start = IDENT)
750
- return unless val = tok(start) || interpolation
1110
+ val = tok(start) || interpolation || tok(IDENT_HYPHEN_INTERP, true)
1111
+ return unless val
751
1112
  res = [val]
752
- while val = tok(NAME) || interpolation
1113
+ while (val = tok(NAME) || interpolation)
753
1114
  res << val
754
1115
  end
755
1116
  res
756
1117
  end
757
1118
 
1119
+ def interp_ident_or_var
1120
+ id = interp_ident
1121
+ return id if id
1122
+ var = var_expr
1123
+ return [var] if var
1124
+ end
1125
+
758
1126
  def interp_name
759
1127
  interp_ident NAME
760
1128
  end
@@ -768,27 +1136,45 @@ MESSAGE
768
1136
  end
769
1137
 
770
1138
  def str?
1139
+ pos = @scanner.pos
1140
+ line = @line
1141
+ offset = @offset
771
1142
  @strs.push ""
772
- yield && @strs.last
1143
+ throw_error {yield} && @strs.last
1144
+ rescue Sass::SyntaxError
1145
+ @scanner.pos = pos
1146
+ @line = line
1147
+ @offset = offset
1148
+ nil
773
1149
  ensure
774
1150
  @strs.pop
775
1151
  end
776
1152
 
777
- def node(node)
778
- node.line = @line
1153
+ def node(node, start_pos, end_pos = source_position)
1154
+ node.line = start_pos.line
1155
+ node.source_range = range(start_pos, end_pos)
779
1156
  node
780
1157
  end
781
1158
 
782
1159
  @sass_script_parser = Class.new(Sass::Script::Parser)
783
1160
  @sass_script_parser.send(:include, ScriptParser)
784
- # @private
785
- def self.sass_script_parser; @sass_script_parser; end
1161
+
1162
+ class << self
1163
+ # @private
1164
+ attr_accessor :sass_script_parser
1165
+ end
786
1166
 
787
1167
  def sass_script(*args)
788
- parser = self.class.sass_script_parser.new(@scanner, @line,
789
- @scanner.pos - (@scanner.string[0...@scanner.pos].rindex("\n") || 0))
1168
+ parser = self.class.sass_script_parser.new(@scanner, @line, @offset,
1169
+ :filename => @filename, :importer => @importer)
790
1170
  result = parser.send(*args)
1171
+ unless @strs.empty?
1172
+ # Convert to CSS manually so that comments are ignored.
1173
+ src = result.to_sass
1174
+ @strs.each {|s| s << src}
1175
+ end
791
1176
  @line = parser.line
1177
+ @offset = parser.offset
792
1178
  result
793
1179
  rescue Sass::SyntaxError => e
794
1180
  throw(:_sass_parser_error, true) if @throw_error
@@ -801,32 +1187,45 @@ MESSAGE
801
1187
 
802
1188
  EXPR_NAMES = {
803
1189
  :media_query => "media query (e.g. print, screen, print and screen)",
804
- :media_expr => "media expression (e.g. (min-device-width: 800px)))",
805
- :pseudo_expr => "expression (e.g. fr, 2n+1)",
1190
+ :media_query_list => "media query (e.g. print, screen, print and screen)",
1191
+ :media_expr => "media expression (e.g. (min-device-width: 800px))",
1192
+ :at_root_query => "@at-root query (e.g. (without: media))",
1193
+ :at_root_directive_list => '* or identifier',
1194
+ :pseudo_arg => "expression (e.g. fr, 2n+1)",
806
1195
  :interp_ident => "identifier",
807
1196
  :interp_name => "identifier",
1197
+ :qualified_name => "identifier",
808
1198
  :expr => "expression (e.g. 1px, bold)",
809
1199
  :_selector => "selector",
810
1200
  :selector_comma_sequence => "selector",
811
1201
  :simple_selector_sequence => "selector",
812
1202
  :import_arg => "file to import (string or url())",
1203
+ :moz_document_function => "matching function (e.g. url-prefix(), domain())",
1204
+ :supports_condition => "@supports condition (e.g. (display: flexbox))",
1205
+ :supports_condition_in_parens => "@supports condition (e.g. (display: flexbox))",
813
1206
  }
814
1207
 
815
- TOK_NAMES = Sass::Util.to_hash(
816
- Sass::SCSS::RX.constants.map {|c| [Sass::SCSS::RX.const_get(c), c.downcase]}).
817
- merge(IDENT => "identifier", /[;}]/ => '";"')
1208
+ TOK_NAMES = Sass::Util.to_hash(Sass::SCSS::RX.constants.map do |c|
1209
+ [Sass::SCSS::RX.const_get(c), c.downcase]
1210
+ end).merge(
1211
+ IDENT => "identifier",
1212
+ /[;}]/ => '";"',
1213
+ /\b(without|with)\b/ => '"with" or "without"'
1214
+ )
818
1215
 
819
1216
  def tok?(rx)
820
1217
  @scanner.match?(rx)
821
1218
  end
822
1219
 
823
1220
  def expr!(name)
824
- (e = send(name)) && (return e)
1221
+ e = send(name)
1222
+ return e if e
825
1223
  expected(EXPR_NAMES[name] || name.to_s)
826
1224
  end
827
1225
 
828
1226
  def tok!(rx)
829
- (t = tok(rx)) && (return t)
1227
+ t = tok(rx)
1228
+ return t if t
830
1229
  name = TOK_NAMES[rx]
831
1230
 
832
1231
  unless name
@@ -848,14 +1247,23 @@ MESSAGE
848
1247
  raise Sass::SyntaxError.new(msg, :line => @line)
849
1248
  end
850
1249
 
1250
+ def throw_error
1251
+ old_throw_error, @throw_error = @throw_error, false
1252
+ yield
1253
+ ensure
1254
+ @throw_error = old_throw_error
1255
+ end
1256
+
851
1257
  def catch_error(&block)
852
1258
  old_throw_error, @throw_error = @throw_error, true
853
1259
  pos = @scanner.pos
854
1260
  line = @line
1261
+ offset = @offset
855
1262
  expected = @expected
856
- if catch(:_sass_parser_error, &block)
1263
+ if catch(:_sass_parser_error) {yield; false}
857
1264
  @scanner.pos = pos
858
1265
  @line = line
1266
+ @offset = offset
859
1267
  @expected = expected
860
1268
  {:pos => pos, :line => line, :expected => @expected, :block => block}
861
1269
  end
@@ -864,10 +1272,10 @@ MESSAGE
864
1272
  end
865
1273
 
866
1274
  def rethrow(err)
867
- if @throw_err
1275
+ if @throw_error
868
1276
  throw :_sass_parser_error, err
869
1277
  else
870
- @scanner = StringScanner.new(@scanner.string)
1278
+ @scanner = Sass::Util::MultibyteStringScanner.new(@scanner.string)
871
1279
  @scanner.pos = err[:pos]
872
1280
  @line = err[:line]
873
1281
  @expected = err[:expected]
@@ -904,10 +1312,29 @@ MESSAGE
904
1312
  # This is important because `#tok` is called all the time.
905
1313
  NEWLINE = "\n"
906
1314
 
907
- def tok(rx)
1315
+ def tok(rx, last_group_lookahead = false)
908
1316
  res = @scanner.scan(rx)
909
1317
  if res
910
- @line += res.count(NEWLINE)
1318
+ # This fixes https://github.com/nex3/sass/issues/104, which affects
1319
+ # Ruby 1.8.7 and REE. This fix is to replace the ?= zero-width
1320
+ # positive lookahead operator in the Regexp (which matches without
1321
+ # consuming the matched group), with a match that does consume the
1322
+ # group, but then rewinds the scanner and removes the group from the
1323
+ # end of the matched string. This fix makes the assumption that the
1324
+ # matched group will always occur at the end of the match.
1325
+ if last_group_lookahead && @scanner[-1]
1326
+ @scanner.pos -= @scanner[-1].length
1327
+ res.slice!(-@scanner[-1].length..-1)
1328
+ end
1329
+
1330
+ newline_count = res.count(NEWLINE)
1331
+ if newline_count > 0
1332
+ @line += newline_count
1333
+ @offset = res[res.rindex(NEWLINE)..-1].size
1334
+ else
1335
+ @offset += res.size
1336
+ end
1337
+
911
1338
  @expected = nil
912
1339
  if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
913
1340
  @strs.each {|s| s << res}