sass 3.2.7 → 3.3.0.rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. data/MIT-LICENSE +2 -2
  2. data/README.md +14 -2
  3. data/Rakefile +25 -1
  4. data/VERSION +1 -1
  5. data/VERSION_DATE +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/lib/sass/cache_stores/base.rb +4 -2
  8. data/lib/sass/cache_stores/chain.rb +2 -1
  9. data/lib/sass/cache_stores/filesystem.rb +2 -6
  10. data/lib/sass/cache_stores/memory.rb +1 -1
  11. data/lib/sass/cache_stores/null.rb +2 -2
  12. data/lib/sass/callbacks.rb +1 -0
  13. data/lib/sass/css.rb +10 -10
  14. data/lib/sass/engine.rb +403 -150
  15. data/lib/sass/environment.rb +136 -57
  16. data/lib/sass/error.rb +7 -7
  17. data/lib/sass/exec.rb +123 -39
  18. data/lib/sass/features.rb +41 -0
  19. data/lib/sass/importers/base.rb +33 -2
  20. data/lib/sass/importers/deprecated_path.rb +45 -0
  21. data/lib/sass/importers/filesystem.rb +25 -14
  22. data/lib/sass/importers.rb +1 -0
  23. data/lib/sass/logger/base.rb +3 -3
  24. data/lib/sass/logger/log_level.rb +4 -6
  25. data/lib/sass/media.rb +19 -19
  26. data/lib/sass/plugin/compiler.rb +141 -101
  27. data/lib/sass/plugin/configuration.rb +18 -22
  28. data/lib/sass/plugin/merb.rb +1 -1
  29. data/lib/sass/plugin/staleness_checker.rb +24 -8
  30. data/lib/sass/plugin.rb +4 -2
  31. data/lib/sass/repl.rb +3 -3
  32. data/lib/sass/script/css_lexer.rb +9 -4
  33. data/lib/sass/script/css_parser.rb +6 -2
  34. data/lib/sass/script/functions.rb +1343 -590
  35. data/lib/sass/script/lexer.rb +84 -52
  36. data/lib/sass/script/parser.rb +217 -97
  37. data/lib/sass/script/tree/funcall.rb +290 -0
  38. data/lib/sass/script/{interpolation.rb → tree/interpolation.rb} +34 -13
  39. data/lib/sass/script/tree/list_literal.rb +80 -0
  40. data/lib/sass/script/tree/literal.rb +47 -0
  41. data/lib/sass/script/tree/map_literal.rb +64 -0
  42. data/lib/sass/script/{node.rb → tree/node.rb} +22 -12
  43. data/lib/sass/script/{operation.rb → tree/operation.rb} +17 -25
  44. data/lib/sass/script/tree/selector.rb +30 -0
  45. data/lib/sass/script/{string_interpolation.rb → tree/string_interpolation.rb} +5 -4
  46. data/lib/sass/script/{unary_operation.rb → tree/unary_operation.rb} +14 -9
  47. data/lib/sass/script/tree/variable.rb +57 -0
  48. data/lib/sass/script/tree.rb +16 -0
  49. data/lib/sass/script/{arg_list.rb → value/arg_list.rb} +4 -24
  50. data/lib/sass/script/value/base.rb +248 -0
  51. data/lib/sass/script/value/bool.rb +36 -0
  52. data/lib/sass/script/{color.rb → value/color.rb} +239 -195
  53. data/lib/sass/script/value/helpers.rb +155 -0
  54. data/lib/sass/script/value/list.rb +119 -0
  55. data/lib/sass/script/value/map.rb +70 -0
  56. data/lib/sass/script/value/null.rb +45 -0
  57. data/lib/sass/script/{number.rb → value/number.rb} +91 -65
  58. data/lib/sass/script/{string.rb → value/string.rb} +9 -11
  59. data/lib/sass/script/value.rb +11 -0
  60. data/lib/sass/script.rb +35 -8
  61. data/lib/sass/scss/css_parser.rb +2 -1
  62. data/lib/sass/scss/parser.rb +338 -170
  63. data/lib/sass/scss/rx.rb +5 -6
  64. data/lib/sass/scss/script_lexer.rb +1 -0
  65. data/lib/sass/scss/script_parser.rb +1 -0
  66. data/lib/sass/scss/static_parser.rb +23 -6
  67. data/lib/sass/selector/abstract_sequence.rb +2 -2
  68. data/lib/sass/selector/comma_sequence.rb +21 -16
  69. data/lib/sass/selector/sequence.rb +60 -34
  70. data/lib/sass/selector/simple.rb +11 -12
  71. data/lib/sass/selector/simple_sequence.rb +55 -33
  72. data/lib/sass/selector.rb +52 -48
  73. data/lib/sass/source/map.rb +211 -0
  74. data/lib/sass/source/position.rb +39 -0
  75. data/lib/sass/source/range.rb +41 -0
  76. data/lib/sass/stack.rb +120 -0
  77. data/lib/sass/supports.rb +12 -13
  78. data/lib/sass/tree/at_root_node.rb +82 -0
  79. data/lib/sass/tree/comment_node.rb +3 -3
  80. data/lib/sass/tree/css_import_node.rb +11 -11
  81. data/lib/sass/tree/debug_node.rb +2 -2
  82. data/lib/sass/tree/directive_node.rb +13 -2
  83. data/lib/sass/tree/each_node.rb +8 -8
  84. data/lib/sass/tree/extend_node.rb +13 -6
  85. data/lib/sass/tree/for_node.rb +4 -4
  86. data/lib/sass/tree/function_node.rb +5 -4
  87. data/lib/sass/tree/if_node.rb +1 -1
  88. data/lib/sass/tree/import_node.rb +4 -5
  89. data/lib/sass/tree/media_node.rb +4 -14
  90. data/lib/sass/tree/mixin_def_node.rb +4 -4
  91. data/lib/sass/tree/mixin_node.rb +21 -8
  92. data/lib/sass/tree/node.rb +29 -12
  93. data/lib/sass/tree/prop_node.rb +38 -18
  94. data/lib/sass/tree/return_node.rb +3 -2
  95. data/lib/sass/tree/root_node.rb +19 -3
  96. data/lib/sass/tree/rule_node.rb +25 -17
  97. data/lib/sass/tree/supports_node.rb +0 -13
  98. data/lib/sass/tree/trace_node.rb +2 -1
  99. data/lib/sass/tree/variable_node.rb +9 -3
  100. data/lib/sass/tree/visitors/base.rb +6 -6
  101. data/lib/sass/tree/visitors/check_nesting.rb +12 -9
  102. data/lib/sass/tree/visitors/convert.rb +63 -38
  103. data/lib/sass/tree/visitors/cssize.rb +63 -23
  104. data/lib/sass/tree/visitors/deep_copy.rb +6 -5
  105. data/lib/sass/tree/visitors/extend.rb +7 -7
  106. data/lib/sass/tree/visitors/perform.rb +256 -151
  107. data/lib/sass/tree/visitors/set_options.rb +6 -6
  108. data/lib/sass/tree/visitors/to_css.rb +231 -81
  109. data/lib/sass/tree/warn_node.rb +2 -2
  110. data/lib/sass/tree/while_node.rb +2 -2
  111. data/lib/sass/util/multibyte_string_scanner.rb +2 -0
  112. data/lib/sass/util/normalized_map.rb +65 -0
  113. data/lib/sass/util/ordered_hash.rb +188 -0
  114. data/lib/sass/util/subset_map.rb +3 -2
  115. data/lib/sass/util/test.rb +9 -0
  116. data/lib/sass/util.rb +220 -34
  117. data/lib/sass/version.rb +9 -9
  118. data/lib/sass.rb +14 -7
  119. data/test/sass/compiler_test.rb +213 -0
  120. data/test/sass/conversion_test.rb +235 -9
  121. data/test/sass/engine_test.rb +230 -60
  122. data/test/sass/exec_test.rb +86 -0
  123. data/test/sass/extend_test.rb +215 -147
  124. data/test/sass/functions_test.rb +584 -99
  125. data/test/sass/importer_test.rb +165 -17
  126. data/test/sass/plugin_test.rb +19 -13
  127. data/test/sass/script_conversion_test.rb +40 -0
  128. data/test/sass/script_test.rb +231 -21
  129. data/test/sass/scss/css_test.rb +14 -5
  130. data/test/sass/scss/scss_test.rb +1266 -66
  131. data/test/sass/source_map_test.rb +879 -0
  132. data/test/sass/templates/bork5.sass +3 -0
  133. data/test/sass/util/normalized_map_test.rb +30 -0
  134. data/test/sass/util_test.rb +90 -0
  135. data/test/sass/value_helpers_test.rb +181 -0
  136. data/test/test_helper.rb +7 -2
  137. metadata +316 -291
  138. data/lib/sass/script/bool.rb +0 -18
  139. data/lib/sass/script/funcall.rb +0 -231
  140. data/lib/sass/script/list.rb +0 -84
  141. data/lib/sass/script/literal.rb +0 -239
  142. data/lib/sass/script/null.rb +0 -34
  143. data/lib/sass/script/variable.rb +0 -58
  144. data/test/Gemfile +0 -3
  145. data/vendor/listen/CHANGELOG.md +0 -221
  146. data/vendor/listen/CONTRIBUTING.md +0 -38
  147. data/vendor/listen/Gemfile +0 -30
  148. data/vendor/listen/Guardfile +0 -8
  149. data/vendor/listen/LICENSE +0 -20
  150. data/vendor/listen/README.md +0 -315
  151. data/vendor/listen/Rakefile +0 -47
  152. data/vendor/listen/Vagrantfile +0 -96
  153. data/vendor/listen/lib/listen/adapter.rb +0 -214
  154. data/vendor/listen/lib/listen/adapters/bsd.rb +0 -112
  155. data/vendor/listen/lib/listen/adapters/darwin.rb +0 -85
  156. data/vendor/listen/lib/listen/adapters/linux.rb +0 -113
  157. data/vendor/listen/lib/listen/adapters/polling.rb +0 -67
  158. data/vendor/listen/lib/listen/adapters/windows.rb +0 -87
  159. data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
  160. data/vendor/listen/lib/listen/directory_record.rb +0 -371
  161. data/vendor/listen/lib/listen/listener.rb +0 -225
  162. data/vendor/listen/lib/listen/multi_listener.rb +0 -143
  163. data/vendor/listen/lib/listen/turnstile.rb +0 -28
  164. data/vendor/listen/lib/listen/version.rb +0 -3
  165. data/vendor/listen/lib/listen.rb +0 -40
  166. data/vendor/listen/listen.gemspec +0 -22
  167. data/vendor/listen/spec/listen/adapter_spec.rb +0 -183
  168. data/vendor/listen/spec/listen/adapters/bsd_spec.rb +0 -36
  169. data/vendor/listen/spec/listen/adapters/darwin_spec.rb +0 -37
  170. data/vendor/listen/spec/listen/adapters/linux_spec.rb +0 -47
  171. data/vendor/listen/spec/listen/adapters/polling_spec.rb +0 -68
  172. data/vendor/listen/spec/listen/adapters/windows_spec.rb +0 -30
  173. data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
  174. data/vendor/listen/spec/listen/directory_record_spec.rb +0 -1225
  175. data/vendor/listen/spec/listen/listener_spec.rb +0 -169
  176. data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
  177. data/vendor/listen/spec/listen/turnstile_spec.rb +0 -56
  178. data/vendor/listen/spec/listen_spec.rb +0 -73
  179. data/vendor/listen/spec/spec_helper.rb +0 -21
  180. data/vendor/listen/spec/support/adapter_helper.rb +0 -629
  181. data/vendor/listen/spec/support/directory_record_helper.rb +0 -55
  182. data/vendor/listen/spec/support/fixtures_helper.rb +0 -29
  183. data/vendor/listen/spec/support/listeners_helper.rb +0 -156
  184. data/vendor/listen/spec/support/platform_helper.rb +0 -15
@@ -0,0 +1,879 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+ require File.dirname(__FILE__) + '/../test_helper'
4
+ require File.dirname(__FILE__) + '/test_helper'
5
+
6
+ class SourcemapTest < Test::Unit::TestCase
7
+ def test_to_json_requires_args
8
+ _, sourcemap = render_with_sourcemap('')
9
+ assert_raise(ArgumentError) {sourcemap.to_json({})}
10
+ assert_raise(ArgumentError) {sourcemap.to_json({:css_path => 'foo'})}
11
+ assert_raise(ArgumentError) {sourcemap.to_json({:sourcemap_path => 'foo'})}
12
+ end
13
+
14
+ def test_simple_mapping_scss
15
+ assert_parses_with_sourcemap <<SCSS, <<CSS, <<JSON
16
+ a {
17
+ foo: bar;
18
+ /* SOME COMMENT */
19
+ font-size: 12px;
20
+ }
21
+ SCSS
22
+ a {
23
+ foo: bar;
24
+ /* SOME COMMENT */
25
+ font-size: 12px; }
26
+
27
+ /*# sourceMappingURL=test.css.map */
28
+ CSS
29
+ {
30
+ "version": "3",
31
+ "mappings": "AAAA,CAAE;EACA,GAAG,EAAE,GAAG;;EAER,SAAS,EAAE,IAAI",
32
+ "sources": ["test_simple_mapping_scss_inline.scss"],
33
+ "file": "test.css"
34
+ }
35
+ JSON
36
+ end
37
+
38
+ def test_simple_mapping_sass
39
+ assert_parses_with_sourcemap <<SASS, <<CSS, <<JSON, :syntax => :sass
40
+ a
41
+ foo: bar
42
+ /* SOME COMMENT */
43
+ :font-size 12px
44
+ SASS
45
+ a {
46
+ foo: bar;
47
+ /* SOME COMMENT */
48
+ font-size: 12px; }
49
+
50
+ /*# sourceMappingURL=test.css.map */
51
+ CSS
52
+ {
53
+ "version": "3",
54
+ "mappings": "AAAA,CAAC;EACC,GAAG,EAAE,GAAG;;EAEP,SAAS,EAAC,IAAI",
55
+ "sources": ["test_simple_mapping_sass_inline.sass"],
56
+ "file": "test.css"
57
+ }
58
+ JSON
59
+ end
60
+
61
+ def test_mapping_with_directory_scss
62
+ options = {:filename => "scss/style.scss", :output => "css/style.css"}
63
+ assert_parses_with_sourcemap <<SCSS, <<CSS, <<JSON, options
64
+ a {
65
+ foo: bar;
66
+ /* SOME COMMENT */
67
+ font-size: 12px;
68
+ }
69
+ SCSS
70
+ a {
71
+ foo: bar;
72
+ /* SOME COMMENT */
73
+ font-size: 12px; }
74
+
75
+ /*# sourceMappingURL=style.css.map */
76
+ CSS
77
+ {
78
+ "version": "3",
79
+ "mappings": "AAAA,CAAE;EACA,GAAG,EAAE,GAAG;;EAER,SAAS,EAAE,IAAI",
80
+ "sources": ["../scss/style.scss"],
81
+ "file": "style.css"
82
+ }
83
+ JSON
84
+ end
85
+
86
+ def test_mapping_with_directory_sass
87
+ options = {:filename => "sass/style.sass", :output => "css/style.css", :syntax => :sass}
88
+ assert_parses_with_sourcemap <<SASS, <<CSS, <<JSON, options
89
+ a
90
+ foo: bar
91
+ /* SOME COMMENT */
92
+ :font-size 12px
93
+ SASS
94
+ a {
95
+ foo: bar;
96
+ /* SOME COMMENT */
97
+ font-size: 12px; }
98
+
99
+ /*# sourceMappingURL=style.css.map */
100
+ CSS
101
+ {
102
+ "version": "3",
103
+ "mappings": "AAAA,CAAC;EACC,GAAG,EAAE,GAAG;;EAEP,SAAS,EAAC,IAAI",
104
+ "sources": ["../sass/style.sass"],
105
+ "file": "style.css"
106
+ }
107
+ JSON
108
+ end
109
+
110
+ unless Sass::Util.ruby1_8?
111
+ def test_simple_charset_mapping_scss
112
+ assert_parses_with_sourcemap <<SCSS, <<CSS, <<JSON
113
+ a {
114
+ fóó: bár;
115
+ }
116
+ SCSS
117
+ @charset "UTF-8";
118
+ a {
119
+ fóó: bár; }
120
+
121
+ /*# sourceMappingURL=test.css.map */
122
+ CSS
123
+ {
124
+ "version": "3",
125
+ "mappings": ";AAAA,CAAE;EACA,GAAG,EAAE,GAAG",
126
+ "sources": ["test_simple_charset_mapping_scss_inline.scss"],
127
+ "file": "test.css"
128
+ }
129
+ JSON
130
+ end
131
+
132
+ def test_simple_charset_mapping_sass
133
+ assert_parses_with_sourcemap <<SASS, <<CSS, <<JSON, :syntax => :sass
134
+ a
135
+ fóó: bár
136
+ SASS
137
+ @charset "UTF-8";
138
+ a {
139
+ fóó: bár; }
140
+
141
+ /*# sourceMappingURL=test.css.map */
142
+ CSS
143
+ {
144
+ "version": "3",
145
+ "mappings": ";AAAA,CAAC;EACC,GAAG,EAAE,GAAG",
146
+ "sources": ["test_simple_charset_mapping_sass_inline.sass"],
147
+ "file": "test.css"
148
+ }
149
+ JSON
150
+ end
151
+
152
+ def test_different_charset_than_encoding_scss
153
+ assert_parses_with_sourcemap(<<SCSS.force_encoding("IBM866"), <<CSS.force_encoding("IBM866"), <<JSON)
154
+ @charset "IBM866";
155
+ f\x86\x86 {
156
+ \x86: b;
157
+ }
158
+ SCSS
159
+ @charset "IBM866";
160
+ f\x86\x86 {
161
+ \x86: b; }
162
+
163
+ /*# sourceMappingURL=test.css.map */
164
+ CSS
165
+ {
166
+ "version": "3",
167
+ "mappings": ";AACA,GAAI;EACF,CAAC,EAAE,CAAC",
168
+ "sources": ["test_different_charset_than_encoding_scss_inline.scss"],
169
+ "file": "test.css"
170
+ }
171
+ JSON
172
+ end
173
+
174
+ def test_different_charset_than_encoding_sass
175
+ assert_parses_with_sourcemap(<<SASS.force_encoding("IBM866"), <<CSS.force_encoding("IBM866"), <<JSON, :syntax => :sass)
176
+ @charset "IBM866"
177
+ f\x86\x86
178
+ \x86: b
179
+ SASS
180
+ @charset "IBM866";
181
+ f\x86\x86 {
182
+ \x86: b; }
183
+
184
+ /*# sourceMappingURL=test.css.map */
185
+ CSS
186
+ {
187
+ "version": "3",
188
+ "mappings": ";AACA,GAAG;EACD,CAAC,EAAE,CAAC",
189
+ "sources": ["test_different_charset_than_encoding_sass_inline.sass"],
190
+ "file": "test.css"
191
+ }
192
+ JSON
193
+ end
194
+ end
195
+
196
+ def test_import_sourcemap_scss
197
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
198
+ @import {{1}}url(foo){{/1}},{{2}}url(moo) {{/2}}, {{3}}url(bar) {{/3}};
199
+ @import {{4}}url(baz) screen print{{/4}};
200
+ SCSS
201
+ {{1}}@import url(foo){{/1}};
202
+ {{2}}@import url(moo){{/2}};
203
+ {{3}}@import url(bar){{/3}};
204
+ {{4}}@import url(baz) screen print{{/4}};
205
+
206
+ /*# sourceMappingURL=test.css.map */
207
+ CSS
208
+ end
209
+
210
+ def test_import_sourcemap_sass
211
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
212
+ @import {{1}}foo.css{{/1}},{{2}}moo.css{{/2}}, {{3}}bar.css{{/3}}
213
+ @import {{4}}url(baz.css){{/4}}
214
+ @import {{5}}url(qux.css) screen print{{/5}}
215
+ SASS
216
+ {{1}}@import url(foo.css){{/1}};
217
+ {{2}}@import url(moo.css){{/2}};
218
+ {{3}}@import url(bar.css){{/3}};
219
+ {{4}}@import url(baz.css){{/4}};
220
+ {{5}}@import url(qux.css) screen print{{/5}};
221
+
222
+ /*# sourceMappingURL=test.css.map */
223
+ CSS
224
+ end
225
+
226
+ def test_media_sourcemap_scss
227
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
228
+ {{1}}@media screen, tv {{/1}}{
229
+ {{2}}body {{/2}}{
230
+ {{3}}max-width{{/3}}: {{4}}1070px{{/4}};
231
+ }
232
+ }
233
+ SCSS
234
+ {{1}}@media screen, tv{{/1}} {
235
+ {{2}}body{{/2}} {
236
+ {{3}}max-width{{/3}}: {{4}}1070px{{/4}}; } }
237
+
238
+ /*# sourceMappingURL=test.css.map */
239
+ CSS
240
+ end
241
+
242
+ def test_media_sourcemap_sass
243
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
244
+ {{1}}@media screen, tv{{/1}}
245
+ {{2}}body{{/2}}
246
+ {{3}}max-width{{/3}}: {{4}}1070px{{/4}}
247
+ SASS
248
+ {{1}}@media screen, tv{{/1}} {
249
+ {{2}}body{{/2}} {
250
+ {{3}}max-width{{/3}}: {{4}}1070px{{/4}}; } }
251
+
252
+ /*# sourceMappingURL=test.css.map */
253
+ CSS
254
+ end
255
+
256
+ def test_interpolation_and_vars_sourcemap_scss
257
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
258
+ $te: "te";
259
+ $teal: {{5}}teal{{/5}};
260
+ {{1}}p {{/1}}{
261
+ {{2}}con#{$te}nt{{/2}}: {{3}}"I a#{$te} #{5 + 10} pies!"{{/3}};
262
+ {{4}}color{{/4}}: $teal;
263
+ }
264
+
265
+ $name: foo;
266
+ $attr: border;
267
+ {{6}}p.#{$name} {{/6}}{
268
+ {{7}}#{$attr}-color{{/7}}: {{8}}blue{{/8}};
269
+ $font-size: 12px;
270
+ $line-height: 30px;
271
+ {{9}}font{{/9}}: {{10}}#{$font-size}/#{$line-height}{{/10}};
272
+ }
273
+ SCSS
274
+ {{1}}p{{/1}} {
275
+ {{2}}content{{/2}}: {{3}}"I ate 15 pies!"{{/3}};
276
+ {{4}}color{{/4}}: {{5}}teal{{/5}}; }
277
+
278
+ {{6}}p.foo{{/6}} {
279
+ {{7}}border-color{{/7}}: {{8}}blue{{/8}};
280
+ {{9}}font{{/9}}: {{10}}12px/30px{{/10}}; }
281
+
282
+ /*# sourceMappingURL=test.css.map */
283
+ CSS
284
+ end
285
+
286
+ def test_interpolation_and_vars_sourcemap_sass
287
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
288
+ $te: "te"
289
+ $teal: {{5}}teal{{/5}}
290
+ {{1}}p{{/1}}
291
+ {{2}}con#{$te}nt{{/2}}: {{3}}"I a#{$te} #{5 + 10} pies!"{{/3}}
292
+ {{4}}color{{/4}}: $teal
293
+
294
+ $name: foo
295
+ $attr: border
296
+ {{6}}p.#{$name}{{/6}}
297
+ {{7}}#{$attr}-color{{/7}}: {{8}}blue{{/8}}
298
+ $font-size: 12px
299
+ $line-height: 30px
300
+ {{9}}font{{/9}}: {{10}}#{$font-size}/#{$line-height}{{/10}}
301
+ SASS
302
+ {{1}}p{{/1}} {
303
+ {{2}}content{{/2}}: {{3}}"I ate 15 pies!"{{/3}};
304
+ {{4}}color{{/4}}: {{5}}teal{{/5}}; }
305
+
306
+ {{6}}p.foo{{/6}} {
307
+ {{7}}border-color{{/7}}: {{8}}blue{{/8}};
308
+ {{9}}font{{/9}}: {{10}}12px/30px{{/10}}; }
309
+
310
+ /*# sourceMappingURL=test.css.map */
311
+ CSS
312
+ end
313
+
314
+ def test_selectors_properties_sourcemap_scss
315
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
316
+ $width: 2px;
317
+ $translucent-red: rgba(255, 0, 0, 0.5);
318
+ {{1}}a {{/1}}{
319
+ {{8}}.special {{/8}}{
320
+ {{9}}color{{/9}}: {{10}}red{{/10}};
321
+ {{11}}&:hover {{/11}}{
322
+ {{12}}foo{{/12}}: {{13}}bar{{/13}};
323
+ {{14}}cursor{{/14}}: {{15}}e + -resize{{/15}};
324
+ {{16}}color{{/16}}: {{17}}opacify($translucent-red, 0.3){{/17}};
325
+ }
326
+ {{18}}&:after {{/18}}{
327
+ {{19}}content{{/19}}: {{20}}"I ate #{5 + 10} pies #{$width} thick!"{{/20}};
328
+ }
329
+ }
330
+ {{21}}&:active {{/21}}{
331
+ {{22}}color{{/22}}: {{23}}#010203 + #040506{{/23}};
332
+ {{24}}border{{/24}}: {{25}}$width solid black{{/25}};
333
+ }
334
+ /* SOME COMMENT */
335
+ {{2}}font{{/2}}: {{3}}2px/3px {{/3}}{
336
+ {{4}}family{{/4}}: {{5}}fantasy{{/5}};
337
+ {{6}}size{{/6}}: {{7}}1em + (2em * 3){{/7}};
338
+ }
339
+ }
340
+ SCSS
341
+ {{1}}a{{/1}} {
342
+ /* SOME COMMENT */
343
+ {{2}}font{{/2}}: {{3}}2px/3px{{/3}};
344
+ {{4}}font-family{{/4}}: {{5}}fantasy{{/5}};
345
+ {{6}}font-size{{/6}}: {{7}}7em{{/7}}; }
346
+ {{8}}a .special{{/8}} {
347
+ {{9}}color{{/9}}: {{10}}red{{/10}}; }
348
+ {{11}}a .special:hover{{/11}} {
349
+ {{12}}foo{{/12}}: {{13}}bar{{/13}};
350
+ {{14}}cursor{{/14}}: {{15}}e-resize{{/15}};
351
+ {{16}}color{{/16}}: {{17}}rgba(255, 0, 0, 0.8){{/17}}; }
352
+ {{18}}a .special:after{{/18}} {
353
+ {{19}}content{{/19}}: {{20}}"I ate 15 pies 2px thick!"{{/20}}; }
354
+ {{21}}a:active{{/21}} {
355
+ {{22}}color{{/22}}: {{23}}#050709{{/23}};
356
+ {{24}}border{{/24}}: {{25}}2px solid black{{/25}}; }
357
+
358
+ /*# sourceMappingURL=test.css.map */
359
+ CSS
360
+ end
361
+
362
+ def test_selectors_properties_sourcemap_sass
363
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
364
+ $width: 2px
365
+ $translucent-red: rgba(255, 0, 0, 0.5)
366
+ {{1}}a{{/1}}
367
+ {{8}}.special{{/8}}
368
+ {{9}}color{{/9}}: {{10}}red{{/10}}
369
+ {{11}}&:hover{{/11}}
370
+ {{12}}foo{{/12}}: {{13}}bar{{/13}}
371
+ {{14}}cursor{{/14}}: {{15}}e + -resize{{/15}}
372
+ {{16}}color{{/16}}: {{17}}opacify($translucent-red, 0.3){{/17}}
373
+ {{18}}&:after{{/18}}
374
+ {{19}}content{{/19}}: {{20}}"I ate #{5 + 10} pies #{$width} thick!"{{/20}}
375
+ {{21}}&:active{{/21}}
376
+ {{22}}color{{/22}}: {{23}}#010203 + #040506{{/23}}
377
+ {{24}}border{{/24}}: {{25}}$width solid black{{/25}}
378
+
379
+ /* SOME COMMENT */
380
+ {{2}}font{{/2}}: {{3}}2px/3px{{/3}}
381
+ {{4}}family{{/4}}: {{5}}fantasy{{/5}}
382
+ {{6}}size{{/6}}: {{7}}1em + (2em * 3){{/7}}
383
+ SASS
384
+ {{1}}a{{/1}} {
385
+ /* SOME COMMENT */
386
+ {{2}}font{{/2}}: {{3}}2px/3px{{/3}};
387
+ {{4}}font-family{{/4}}: {{5}}fantasy{{/5}};
388
+ {{6}}font-size{{/6}}: {{7}}7em{{/7}}; }
389
+ {{8}}a .special{{/8}} {
390
+ {{9}}color{{/9}}: {{10}}red{{/10}}; }
391
+ {{11}}a .special:hover{{/11}} {
392
+ {{12}}foo{{/12}}: {{13}}bar{{/13}};
393
+ {{14}}cursor{{/14}}: {{15}}e-resize{{/15}};
394
+ {{16}}color{{/16}}: {{17}}rgba(255, 0, 0, 0.8){{/17}}; }
395
+ {{18}}a .special:after{{/18}} {
396
+ {{19}}content{{/19}}: {{20}}"I ate 15 pies 2px thick!"{{/20}}; }
397
+ {{21}}a:active{{/21}} {
398
+ {{22}}color{{/22}}: {{23}}#050709{{/23}};
399
+ {{24}}border{{/24}}: {{25}}2px solid black{{/25}}; }
400
+
401
+ /*# sourceMappingURL=test.css.map */
402
+ CSS
403
+ end
404
+
405
+ def test_extend_sourcemap_scss
406
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
407
+ {{1}}.error {{/1}}{
408
+ {{2}}border{{/2}}: {{3}}1px #ff00aa{{/3}};
409
+ {{4}}background-color{{/4}}: {{5}}#fdd{{/5}};
410
+ }
411
+ {{6}}.seriousError {{/6}}{
412
+ @extend .error;
413
+ {{7}}border-width{{/7}}: {{8}}3px{{/8}};
414
+ }
415
+ SCSS
416
+ {{1}}.error, .seriousError{{/1}} {
417
+ {{2}}border{{/2}}: {{3}}1px #ff00aa{{/3}};
418
+ {{4}}background-color{{/4}}: {{5}}#fdd{{/5}}; }
419
+
420
+ {{6}}.seriousError{{/6}} {
421
+ {{7}}border-width{{/7}}: {{8}}3px{{/8}}; }
422
+
423
+ /*# sourceMappingURL=test.css.map */
424
+ CSS
425
+ end
426
+
427
+ def test_extend_sourcemap_sass
428
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
429
+ {{1}}.error{{/1}}
430
+ {{2}}border{{/2}}: {{3}}1px #f00{{/3}}
431
+ {{4}}background-color{{/4}}: {{5}}#fdd{{/5}}
432
+
433
+ {{6}}.seriousError{{/6}}
434
+ @extend .error
435
+ {{7}}border-width{{/7}}: {{8}}3px{{/8}}
436
+ SASS
437
+ {{1}}.error, .seriousError{{/1}} {
438
+ {{2}}border{{/2}}: {{3}}1px red{{/3}};
439
+ {{4}}background-color{{/4}}: {{5}}#ffdddd{{/5}}; }
440
+
441
+ {{6}}.seriousError{{/6}} {
442
+ {{7}}border-width{{/7}}: {{8}}3px{{/8}}; }
443
+
444
+ /*# sourceMappingURL=test.css.map */
445
+ CSS
446
+ end
447
+
448
+ def test_for_sourcemap_scss
449
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
450
+ @for $i from 1 through 3 {
451
+ {{1}}{{4}}{{7}}.item-#{$i} {{/1}}{{/4}}{{/7}}{ {{2}}{{5}}{{8}}width{{/2}}{{/5}}{{/8}}: {{3}}{{6}}{{9}}2em * $i{{/3}}{{/6}}{{/9}}; }
452
+ }
453
+ SCSS
454
+ {{1}}.item-1{{/1}} {
455
+ {{2}}width{{/2}}: {{3}}2em{{/3}}; }
456
+
457
+ {{4}}.item-2{{/4}} {
458
+ {{5}}width{{/5}}: {{6}}4em{{/6}}; }
459
+
460
+ {{7}}.item-3{{/7}} {
461
+ {{8}}width{{/8}}: {{9}}6em{{/9}}; }
462
+
463
+ /*# sourceMappingURL=test.css.map */
464
+ CSS
465
+ end
466
+
467
+ def test_for_sourcemap_sass
468
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
469
+ @for $i from 1 through 3
470
+ {{1}}{{4}}{{7}}.item-#{$i}{{/1}}{{/4}}{{/7}}
471
+ {{2}}{{5}}{{8}}width{{/2}}{{/5}}{{/8}}: {{3}}{{6}}{{9}}2em * $i{{/3}}{{/6}}{{/9}}
472
+ SASS
473
+ {{1}}.item-1{{/1}} {
474
+ {{2}}width{{/2}}: {{3}}2em{{/3}}; }
475
+
476
+ {{4}}.item-2{{/4}} {
477
+ {{5}}width{{/5}}: {{6}}4em{{/6}}; }
478
+
479
+ {{7}}.item-3{{/7}} {
480
+ {{8}}width{{/8}}: {{9}}6em{{/9}}; }
481
+
482
+ /*# sourceMappingURL=test.css.map */
483
+ CSS
484
+ end
485
+
486
+ def test_while_sourcemap_scss
487
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
488
+ $i: 6;
489
+ @while $i > 0 {
490
+ {{1}}{{4}}{{7}}.item-#{$i} {{/1}}{{/4}}{{/7}}{ {{2}}{{5}}{{8}}width{{/2}}{{/5}}{{/8}}: {{3}}{{6}}{{9}}2em * $i{{/3}}{{/6}}{{/9}}; }
491
+ $i: $i - 2 !global;
492
+ }
493
+ SCSS
494
+ {{1}}.item-6{{/1}} {
495
+ {{2}}width{{/2}}: {{3}}12em{{/3}}; }
496
+
497
+ {{4}}.item-4{{/4}} {
498
+ {{5}}width{{/5}}: {{6}}8em{{/6}}; }
499
+
500
+ {{7}}.item-2{{/7}} {
501
+ {{8}}width{{/8}}: {{9}}4em{{/9}}; }
502
+
503
+ /*# sourceMappingURL=test.css.map */
504
+ CSS
505
+ end
506
+
507
+ def test_while_sourcemap_sass
508
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
509
+ $i: 6
510
+ @while $i > 0
511
+ {{1}}{{4}}{{7}}.item-#{$i}{{/1}}{{/4}}{{/7}}
512
+ {{2}}{{5}}{{8}}width{{/2}}{{/5}}{{/8}}: {{3}}{{6}}{{9}}2em * $i{{/3}}{{/6}}{{/9}}
513
+ $i: $i - 2 !global
514
+ SASS
515
+ {{1}}.item-6{{/1}} {
516
+ {{2}}width{{/2}}: {{3}}12em{{/3}}; }
517
+
518
+ {{4}}.item-4{{/4}} {
519
+ {{5}}width{{/5}}: {{6}}8em{{/6}}; }
520
+
521
+ {{7}}.item-2{{/7}} {
522
+ {{8}}width{{/8}}: {{9}}4em{{/9}}; }
523
+
524
+ /*# sourceMappingURL=test.css.map */
525
+ CSS
526
+ end
527
+
528
+ def test_each_sourcemap_scss
529
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
530
+ @each $animal in puma, sea-slug, egret, salamander {
531
+ {{1}}{{4}}{{7}}{{10}}.#{$animal}-icon {{/1}}{{/4}}{{/7}}{{/10}}{
532
+ {{2}}{{5}}{{8}}{{11}}background-image{{/2}}{{/5}}{{/8}}{{/11}}: {{3}}{{6}}{{9}}{{12}}url('/images/#{$animal}.png'){{/3}}{{/6}}{{/9}}{{/12}};
533
+ }
534
+ }
535
+ SCSS
536
+ {{1}}.puma-icon{{/1}} {
537
+ {{2}}background-image{{/2}}: {{3}}url("/images/puma.png"){{/3}}; }
538
+
539
+ {{4}}.sea-slug-icon{{/4}} {
540
+ {{5}}background-image{{/5}}: {{6}}url("/images/sea-slug.png"){{/6}}; }
541
+
542
+ {{7}}.egret-icon{{/7}} {
543
+ {{8}}background-image{{/8}}: {{9}}url("/images/egret.png"){{/9}}; }
544
+
545
+ {{10}}.salamander-icon{{/10}} {
546
+ {{11}}background-image{{/11}}: {{12}}url("/images/salamander.png"){{/12}}; }
547
+
548
+ /*# sourceMappingURL=test.css.map */
549
+ CSS
550
+ end
551
+
552
+ def test_each_sourcemap_sass
553
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
554
+ @each $animal in puma, sea-slug, egret, salamander
555
+ {{1}}{{4}}{{7}}{{10}}.#{$animal}-icon{{/1}}{{/4}}{{/7}}{{/10}}
556
+ {{2}}{{5}}{{8}}{{11}}background-image{{/2}}{{/5}}{{/8}}{{/11}}: {{3}}{{6}}{{9}}{{12}}url('/images/#{$animal}.png'){{/3}}{{/6}}{{/9}}{{/12}}
557
+ SASS
558
+ {{1}}.puma-icon{{/1}} {
559
+ {{2}}background-image{{/2}}: {{3}}url("/images/puma.png"){{/3}}; }
560
+
561
+ {{4}}.sea-slug-icon{{/4}} {
562
+ {{5}}background-image{{/5}}: {{6}}url("/images/sea-slug.png"){{/6}}; }
563
+
564
+ {{7}}.egret-icon{{/7}} {
565
+ {{8}}background-image{{/8}}: {{9}}url("/images/egret.png"){{/9}}; }
566
+
567
+ {{10}}.salamander-icon{{/10}} {
568
+ {{11}}background-image{{/11}}: {{12}}url("/images/salamander.png"){{/12}}; }
569
+
570
+ /*# sourceMappingURL=test.css.map */
571
+ CSS
572
+ end
573
+
574
+ def test_mixin_sourcemap_scss
575
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
576
+ @mixin large-text {
577
+ font: {
578
+ {{2}}size{{/2}}: {{3}}20px{{/3}};
579
+ {{4}}weight{{/4}}: {{5}}bold{{/5}};
580
+ }
581
+ {{6}}color{{/6}}: {{7}}#ff0000{{/7}};
582
+ }
583
+
584
+ {{1}}.page-title {{/1}}{
585
+ @include large-text;
586
+ {{8}}padding{{/8}}: {{9}}4px{{/9}};
587
+ }
588
+
589
+ @mixin dashed-border($color, $width: {{14}}1in{{/14}}) {
590
+ border: {
591
+ {{11}}{{18}}color{{/11}}{{/18}}: $color;
592
+ {{13}}{{20}}width{{/13}}{{/20}}: $width;
593
+ {{15}}{{22}}style{{/15}}{{/22}}: {{16}}{{23}}dashed{{/16}}{{/23}};
594
+ }
595
+ }
596
+
597
+ {{10}}p {{/10}}{ @include dashed-border({{12}}blue{{/12}}); }
598
+ {{17}}h1 {{/17}}{ @include dashed-border({{19}}blue{{/19}}, {{21}}2in{{/21}}); }
599
+
600
+ @mixin box-shadow($shadows...) {
601
+ {{25}}-moz-box-shadow{{/25}}: {{26}}$shadows{{/26}};
602
+ {{27}}-webkit-box-shadow{{/27}}: {{28}}$shadows{{/28}};
603
+ {{29}}box-shadow{{/29}}: {{30}}$shadows{{/30}};
604
+ }
605
+
606
+ {{24}}.shadows {{/24}}{
607
+ @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999);
608
+ }
609
+ SCSS
610
+ {{1}}.page-title{{/1}} {
611
+ {{2}}font-size{{/2}}: {{3}}20px{{/3}};
612
+ {{4}}font-weight{{/4}}: {{5}}bold{{/5}};
613
+ {{6}}color{{/6}}: {{7}}#ff0000{{/7}};
614
+ {{8}}padding{{/8}}: {{9}}4px{{/9}}; }
615
+
616
+ {{10}}p{{/10}} {
617
+ {{11}}border-color{{/11}}: {{12}}blue{{/12}};
618
+ {{13}}border-width{{/13}}: {{14}}1in{{/14}};
619
+ {{15}}border-style{{/15}}: {{16}}dashed{{/16}}; }
620
+
621
+ {{17}}h1{{/17}} {
622
+ {{18}}border-color{{/18}}: {{19}}blue{{/19}};
623
+ {{20}}border-width{{/20}}: {{21}}2in{{/21}};
624
+ {{22}}border-style{{/22}}: {{23}}dashed{{/23}}; }
625
+
626
+ {{24}}.shadows{{/24}} {
627
+ {{25}}-moz-box-shadow{{/25}}: {{26}}0px 4px 5px #666666, 2px 6px 10px #999999{{/26}};
628
+ {{27}}-webkit-box-shadow{{/27}}: {{28}}0px 4px 5px #666666, 2px 6px 10px #999999{{/28}};
629
+ {{29}}box-shadow{{/29}}: {{30}}0px 4px 5px #666666, 2px 6px 10px #999999{{/30}}; }
630
+
631
+ /*# sourceMappingURL=test.css.map */
632
+ CSS
633
+ end
634
+
635
+ def test_mixin_sourcemap_sass
636
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
637
+ =large-text
638
+ :font
639
+ {{2}}size{{/2}}: {{3}}20px{{/3}}
640
+ {{4}}weight{{/4}}: {{5}}bold{{/5}}
641
+ {{6}}color{{/6}}: {{7}}#ff0000{{/7}}
642
+
643
+ {{1}}.page-title{{/1}}
644
+ +large-text
645
+ {{8}}padding{{/8}}: {{9}}4px{{/9}}
646
+
647
+ =dashed-border($color, $width: {{14}}1in{{/14}})
648
+ border:
649
+ {{11}}{{18}}color{{/11}}{{/18}}: $color
650
+ {{13}}{{20}}width{{/13}}{{/20}}: $width
651
+ {{15}}{{22}}style{{/15}}{{/22}}: {{16}}{{23}}dashed{{/16}}{{/23}}
652
+
653
+ {{10}}p{{/10}}
654
+ +dashed-border({{12}}blue{{/12}})
655
+
656
+ {{17}}h1{{/17}}
657
+ +dashed-border({{19}}blue{{/19}}, {{21}}2in{{/21}})
658
+
659
+ =box-shadow($shadows...)
660
+ {{25}}-moz-box-shadow{{/25}}: {{26}}$shadows{{/26}}
661
+ {{27}}-webkit-box-shadow{{/27}}: {{28}}$shadows{{/28}}
662
+ {{29}}box-shadow{{/29}}: {{30}}$shadows{{/30}}
663
+
664
+ {{24}}.shadows{{/24}}
665
+ +box-shadow(0px 4px 5px #666, 2px 6px 10px #999)
666
+ SASS
667
+ {{1}}.page-title{{/1}} {
668
+ {{2}}font-size{{/2}}: {{3}}20px{{/3}};
669
+ {{4}}font-weight{{/4}}: {{5}}bold{{/5}};
670
+ {{6}}color{{/6}}: {{7}}red{{/7}};
671
+ {{8}}padding{{/8}}: {{9}}4px{{/9}}; }
672
+
673
+ {{10}}p{{/10}} {
674
+ {{11}}border-color{{/11}}: {{12}}blue{{/12}};
675
+ {{13}}border-width{{/13}}: {{14}}1in{{/14}};
676
+ {{15}}border-style{{/15}}: {{16}}dashed{{/16}}; }
677
+
678
+ {{17}}h1{{/17}} {
679
+ {{18}}border-color{{/18}}: {{19}}blue{{/19}};
680
+ {{20}}border-width{{/20}}: {{21}}2in{{/21}};
681
+ {{22}}border-style{{/22}}: {{23}}dashed{{/23}}; }
682
+
683
+ {{24}}.shadows{{/24}} {
684
+ {{25}}-moz-box-shadow{{/25}}: {{26}}0px 4px 5px #666666, 2px 6px 10px #999999{{/26}};
685
+ {{27}}-webkit-box-shadow{{/27}}: {{28}}0px 4px 5px #666666, 2px 6px 10px #999999{{/28}};
686
+ {{29}}box-shadow{{/29}}: {{30}}0px 4px 5px #666666, 2px 6px 10px #999999{{/30}}; }
687
+
688
+ /*# sourceMappingURL=test.css.map */
689
+ CSS
690
+ end
691
+
692
+ def test_function_sourcemap_scss
693
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
694
+ $grid-width: 20px;
695
+ $gutter-width: 5px;
696
+
697
+ @function grid-width($n) {
698
+ @return $n * $grid-width + ($n - 1) * $gutter-width;
699
+ }
700
+ {{1}}sidebar {{/1}}{ {{2}}width{{/2}}: {{3}}grid-width(5){{/3}}; }
701
+ SCSS
702
+ {{1}}sidebar{{/1}} {
703
+ {{2}}width{{/2}}: {{3}}120px{{/3}}; }
704
+
705
+ /*# sourceMappingURL=test.css.map */
706
+ CSS
707
+ end
708
+
709
+ def test_function_sourcemap_sass
710
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
711
+ $grid-width: 20px
712
+ $gutter-width: 5px
713
+
714
+ @function grid-width($n)
715
+ @return $n * $grid-width + ($n - 1) * $gutter-width
716
+
717
+ {{1}}sidebar{{/1}}
718
+ {{2}}width{{/2}}: {{3}}grid-width(5){{/3}}
719
+ SASS
720
+ {{1}}sidebar{{/1}} {
721
+ {{2}}width{{/2}}: {{3}}120px{{/3}}; }
722
+
723
+ /*# sourceMappingURL=test.css.map */
724
+ CSS
725
+ end
726
+
727
+ # Regression tests
728
+
729
+ def test_properties_sass
730
+ assert_parses_with_mapping <<SASS, <<CSS, :syntax => :sass
731
+ {{1}}.foo{{/1}}
732
+ :{{2}}name{{/2}} {{3}}value{{/3}}
733
+ {{4}}name{{/4}}: {{5}}value{{/5}}
734
+ :{{6}}name{{/6}} {{7}}value{{/7}}
735
+ {{8}}name{{/8}}: {{9}}value{{/9}}
736
+ SASS
737
+ {{1}}.foo{{/1}} {
738
+ {{2}}name{{/2}}: {{3}}value{{/3}};
739
+ {{4}}name{{/4}}: {{5}}value{{/5}};
740
+ {{6}}name{{/6}}: {{7}}value{{/7}};
741
+ {{8}}name{{/8}}: {{9}}value{{/9}}; }
742
+
743
+ /*# sourceMappingURL=test.css.map */
744
+ CSS
745
+ end
746
+
747
+ private
748
+
749
+ ANNOTATION_REGEX = /\{\{(\/?)([^}]+)\}\}/
750
+
751
+ def build_ranges(text, file_name = nil)
752
+ ranges = Hash.new {|h, k| h[k] = []}
753
+ start_positions = {}
754
+ text.split("\n").each_with_index do |line_text, line|
755
+ line += 1 # lines shoud be 1-based
756
+ while (match = line_text.match(ANNOTATION_REGEX))
757
+ closing = !match[1].empty?
758
+ name = match[2]
759
+ match_offsets = match.offset(0)
760
+ offset = match_offsets[0] + 1 # Offsets are 1-based in source maps.
761
+ assert(!closing || start_positions[name], "Closing annotation #{name} found before opening one.")
762
+ position = Sass::Source::Position.new(line, offset)
763
+ if closing
764
+ ranges[name] << Sass::Source::Range.new(
765
+ start_positions[name], position, file_name,
766
+ Sass::Importers::Filesystem.new('.'))
767
+ start_positions.delete name
768
+ else
769
+ assert(!start_positions[name], "Overlapping range annotation #{name} encountered on line #{line}")
770
+ start_positions[name] = position
771
+ end
772
+ line_text.slice!(match_offsets[0], match_offsets[1] - match_offsets[0])
773
+ end
774
+ end
775
+ ranges
776
+ end
777
+
778
+ def build_mapping_from_annotations(source, css, source_file_name)
779
+ source_ranges = build_ranges(source, source_file_name)
780
+ target_ranges = build_ranges(css)
781
+ map = Sass::Source::Map.new
782
+ Sass::Util.flatten(source_ranges.map do |(name, sources)|
783
+ assert(sources.length == 1, "#{sources.length} source ranges encountered for annotation #{name}")
784
+ assert(target_ranges[name], "No target ranges for annotation #{name}")
785
+ target_ranges[name].map {|target_range| [sources.first, target_range]}
786
+ end, 1).
787
+ sort_by {|(_, target)| [target.start_pos.line, target.start_pos.offset]}.
788
+ each {|(s2, target)| map.add(s2, target)}
789
+ map
790
+ end
791
+
792
+ def assert_parses_with_mapping(source, css, options={})
793
+ options[:syntax] ||= :scss
794
+ input_filename = filename_for_test(options[:syntax])
795
+ mapping = build_mapping_from_annotations(source, css, input_filename)
796
+ source.gsub!(ANNOTATION_REGEX, "")
797
+ css.gsub!(ANNOTATION_REGEX, "")
798
+ rendered, sourcemap = render_with_sourcemap(source, options)
799
+ assert_equal css.rstrip, rendered.rstrip
800
+ assert_sourcemaps_equal source, css, mapping, sourcemap
801
+ end
802
+
803
+ def assert_positions_equal(expected, actual, lines, message = nil)
804
+ prefix = message ? message + ": " : ""
805
+ assert_equal(expected.line, actual.line, prefix +
806
+ "Expected #{expected.inspect} but was #{actual.inspect}")
807
+ assert_equal(expected.offset, actual.offset, prefix +
808
+ "Expected #{expected.inspect} but was #{actual.inspect}\n" +
809
+ lines[actual.line - 1] + "\n" + ("-" * (actual.offset - 1)) + "^")
810
+ end
811
+
812
+ def assert_ranges_equal(expected, actual, lines, prefix)
813
+ assert_positions_equal(expected.start_pos, actual.start_pos, lines, prefix + " start position")
814
+ assert_positions_equal(expected.end_pos, actual.end_pos, lines, prefix + " end position")
815
+ assert_equal(expected.file, actual.file)
816
+ end
817
+
818
+ def assert_sourcemaps_equal(source, css, expected, actual)
819
+ assert_equal(expected.data.length, actual.data.length, <<MESSAGE)
820
+ Wrong number of mappings. Expected:
821
+ #{dump_sourcemap_as_expectation(source, css, expected).gsub(/^/, '| ')}
822
+
823
+ Actual:
824
+ #{dump_sourcemap_as_expectation(source, css, actual).gsub(/^/, '| ')}
825
+ MESSAGE
826
+ source_lines = source.split("\n")
827
+ css_lines = css.split("\n")
828
+ expected.data.zip(actual.data) do |expected_mapping, actual_mapping|
829
+ assert_ranges_equal(expected_mapping.input, actual_mapping.input, source_lines, "Input")
830
+ assert_ranges_equal(expected_mapping.output, actual_mapping.output, css_lines, "Output")
831
+ end
832
+ end
833
+
834
+ def assert_parses_with_sourcemap(source, css, sourcemap_json, options={})
835
+ rendered, sourcemap = render_with_sourcemap(source, options)
836
+ css_path = options[:output] || "test.css"
837
+ sourcemap_path = Sass::Util.sourcemap_name(css_path)
838
+ rendered_json = sourcemap.to_json(:css_path => css_path, :sourcemap_path => sourcemap_path)
839
+
840
+ assert_equal css.rstrip, rendered.rstrip
841
+ assert_equal sourcemap_json.rstrip, rendered_json
842
+ end
843
+
844
+ def render_with_sourcemap(source, options={})
845
+ options[:syntax] ||= :scss
846
+ munge_filename options
847
+ engine = Sass::Engine.new(source, options)
848
+ engine.options[:cache] = false
849
+ sourcemap_path = Sass::Util.sourcemap_name(options[:output] || "test.css")
850
+ engine.render_with_sourcemap File.basename(sourcemap_path)
851
+ end
852
+
853
+ def dump_sourcemap_as_expectation(source, css, sourcemap)
854
+ mappings_to_annotations(source, sourcemap.data.map {|d| d.input}) + "\n\n" +
855
+ "=" * 20 + " maps to:\n\n" +
856
+ mappings_to_annotations(css, sourcemap.data.map {|d| d.output})
857
+ end
858
+
859
+ def mappings_to_annotations(source, ranges)
860
+ additional_offsets = Hash.new(0)
861
+ lines = source.split("\n")
862
+
863
+ add_annotation = lambda do |pos, str|
864
+ line_num = pos.line - 1
865
+ line = lines[line_num]
866
+ offset = pos.offset + additional_offsets[line_num] - 1
867
+ line << " " * (offset - line.length) if offset > line.length
868
+ line.insert(offset, str)
869
+ additional_offsets[line_num] += str.length
870
+ end
871
+
872
+ ranges.each_with_index do |range, i|
873
+ add_annotation[range.start_pos, "{{#{i + 1}}}"]
874
+ add_annotation[range.end_pos, "{{/#{i + 1}}}"]
875
+ end
876
+
877
+ return lines.join("\n")
878
+ end
879
+ end