sass 3.3.14 → 3.4.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +5 -5
  4. data/VERSION +1 -1
  5. data/VERSION_DATE +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/bin/sass +1 -1
  8. data/bin/scss +1 -1
  9. data/lib/sass.rb +0 -5
  10. data/lib/sass/css.rb +1 -3
  11. data/lib/sass/engine.rb +28 -39
  12. data/lib/sass/environment.rb +13 -17
  13. data/lib/sass/error.rb +6 -9
  14. data/lib/sass/exec.rb +5 -771
  15. data/lib/sass/exec/base.rb +187 -0
  16. data/lib/sass/exec/sass_convert.rb +264 -0
  17. data/lib/sass/exec/sass_scss.rb +419 -0
  18. data/lib/sass/features.rb +6 -0
  19. data/lib/sass/importers.rb +0 -1
  20. data/lib/sass/importers/base.rb +5 -1
  21. data/lib/sass/importers/filesystem.rb +4 -21
  22. data/lib/sass/media.rb +1 -4
  23. data/lib/sass/plugin/compiler.rb +32 -136
  24. data/lib/sass/script/css_lexer.rb +1 -1
  25. data/lib/sass/script/functions.rb +363 -39
  26. data/lib/sass/script/lexer.rb +68 -50
  27. data/lib/sass/script/parser.rb +29 -14
  28. data/lib/sass/script/tree.rb +1 -0
  29. data/lib/sass/script/tree/funcall.rb +1 -1
  30. data/lib/sass/script/tree/interpolation.rb +19 -1
  31. data/lib/sass/script/tree/selector.rb +26 -0
  32. data/lib/sass/script/value.rb +0 -1
  33. data/lib/sass/script/value/bool.rb +0 -5
  34. data/lib/sass/script/value/color.rb +32 -12
  35. data/lib/sass/script/value/helpers.rb +107 -0
  36. data/lib/sass/script/value/list.rb +0 -15
  37. data/lib/sass/script/value/null.rb +0 -5
  38. data/lib/sass/script/value/number.rb +60 -14
  39. data/lib/sass/script/value/string.rb +53 -9
  40. data/lib/sass/scss/css_parser.rb +8 -2
  41. data/lib/sass/scss/parser.rb +175 -319
  42. data/lib/sass/scss/rx.rb +14 -5
  43. data/lib/sass/scss/static_parser.rb +298 -1
  44. data/lib/sass/selector.rb +56 -193
  45. data/lib/sass/selector/abstract_sequence.rb +28 -13
  46. data/lib/sass/selector/comma_sequence.rb +91 -12
  47. data/lib/sass/selector/pseudo.rb +256 -0
  48. data/lib/sass/selector/sequence.rb +99 -31
  49. data/lib/sass/selector/simple.rb +14 -25
  50. data/lib/sass/selector/simple_sequence.rb +101 -37
  51. data/lib/sass/shared.rb +1 -1
  52. data/lib/sass/source/map.rb +23 -9
  53. data/lib/sass/stack.rb +0 -6
  54. data/lib/sass/supports.rb +1 -1
  55. data/lib/sass/tree/at_root_node.rb +1 -0
  56. data/lib/sass/tree/directive_node.rb +7 -1
  57. data/lib/sass/tree/error_node.rb +18 -0
  58. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  59. data/lib/sass/tree/prop_node.rb +1 -1
  60. data/lib/sass/tree/rule_node.rb +11 -6
  61. data/lib/sass/tree/visitors/check_nesting.rb +3 -4
  62. data/lib/sass/tree/visitors/convert.rb +8 -17
  63. data/lib/sass/tree/visitors/cssize.rb +12 -24
  64. data/lib/sass/tree/visitors/deep_copy.rb +5 -0
  65. data/lib/sass/tree/visitors/perform.rb +43 -28
  66. data/lib/sass/tree/visitors/set_options.rb +5 -0
  67. data/lib/sass/tree/visitors/to_css.rb +14 -13
  68. data/lib/sass/util.rb +94 -90
  69. data/test/sass/cache_test.rb +1 -1
  70. data/test/sass/callbacks_test.rb +1 -1
  71. data/test/sass/compiler_test.rb +5 -14
  72. data/test/sass/conversion_test.rb +47 -1
  73. data/test/sass/css2sass_test.rb +3 -3
  74. data/test/sass/encoding_test.rb +219 -0
  75. data/test/sass/engine_test.rb +128 -191
  76. data/test/sass/exec_test.rb +2 -2
  77. data/test/sass/extend_test.rb +234 -17
  78. data/test/sass/functions_test.rb +268 -213
  79. data/test/sass/importer_test.rb +31 -21
  80. data/test/sass/logger_test.rb +1 -1
  81. data/test/sass/more_results/more_import.css +1 -1
  82. data/test/sass/plugin_test.rb +12 -11
  83. data/test/sass/results/compact.css +1 -1
  84. data/test/sass/results/complex.css +4 -4
  85. data/test/sass/results/expanded.css +1 -1
  86. data/test/sass/results/import.css +1 -1
  87. data/test/sass/results/import_charset_ibm866.css +2 -2
  88. data/test/sass/results/mixins.css +17 -17
  89. data/test/sass/results/nested.css +1 -1
  90. data/test/sass/results/parent_ref.css +2 -2
  91. data/test/sass/results/script.css +3 -3
  92. data/test/sass/results/scss_import.css +1 -1
  93. data/test/sass/script_conversion_test.rb +7 -4
  94. data/test/sass/script_test.rb +202 -79
  95. data/test/sass/scss/css_test.rb +95 -25
  96. data/test/sass/scss/rx_test.rb +4 -4
  97. data/test/sass/scss/scss_test.rb +363 -19
  98. data/test/sass/source_map_test.rb +48 -41
  99. data/test/sass/superselector_test.rb +191 -0
  100. data/test/sass/templates/scss_import.scss +2 -1
  101. data/test/sass/test_helper.rb +1 -1
  102. data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
  103. data/test/sass/util/normalized_map_test.rb +1 -1
  104. data/test/sass/util/subset_map_test.rb +2 -2
  105. data/test/sass/util_test.rb +1 -1
  106. data/test/sass/value_helpers_test.rb +3 -3
  107. data/test/test_helper.rb +2 -2
  108. metadata +30 -7
  109. data/lib/sass/importers/deprecated_path.rb +0 -51
  110. data/lib/sass/script/value/deprecated_false.rb +0 -55
@@ -3,8 +3,8 @@ module Sass
3
3
  # The abstract parent class of the various selector sequence classes.
4
4
  #
5
5
  # All subclasses should implement a `members` method that returns an array
6
- # of object that respond to `#line=` and `#filename=`, as well as a `to_a`
7
- # method that returns an array of strings and script nodes.
6
+ # of object that respond to `#line=` and `#filename=`, as well as a `to_s`
7
+ # method that returns the string representation of the selector.
8
8
  class AbstractSequence
9
9
  # The line of the Sass template on which this selector was declared.
10
10
  #
@@ -62,22 +62,26 @@ module Sass
62
62
  # Whether or not this selector sequence contains a placeholder selector.
63
63
  # Checks recursively.
64
64
  def has_placeholder?
65
- @has_placeholder ||=
66
- members.any? {|m| m.is_a?(AbstractSequence) ? m.has_placeholder? : m.is_a?(Placeholder)}
65
+ @has_placeholder ||= members.any? do |m|
66
+ next m.has_placeholder? if m.is_a?(AbstractSequence)
67
+ next m.selector && m.selector.has_placeholder? if m.is_a?(Pseudo)
68
+ m.is_a?(Placeholder)
69
+ end
67
70
  end
68
71
 
69
- # Converts the selector into a string. This is the standard selector
70
- # string, along with any SassScript interpolation that may exist.
72
+ # Returns the selector string.
71
73
  #
72
74
  # @return [String]
73
75
  def to_s
74
- to_a.map {|e| e.is_a?(Sass::Script::Tree::Node) ? "\#{#{e.to_sass}}" : e}.join
76
+ Sass::Util.abstract(self)
75
77
  end
76
78
 
77
- # Returns the specificity of the selector as an integer. The base is given
78
- # by {Sass::Selector::SPECIFICITY_BASE}.
79
+ # Returns the specificity of the selector.
79
80
  #
80
- # @return [Fixnum]
81
+ # The base is given by {Sass::Selector::SPECIFICITY_BASE}. This can be a
82
+ # number or a range representing possible specificities.
83
+ #
84
+ # @return [Fixnum, Range]
81
85
  def specificity
82
86
  _specificity(members)
83
87
  end
@@ -85,9 +89,20 @@ module Sass
85
89
  protected
86
90
 
87
91
  def _specificity(arr)
88
- spec = 0
89
- arr.map {|m| spec += m.is_a?(String) ? 0 : m.specificity}
90
- spec
92
+ min = 0
93
+ max = 0
94
+ arr.each do |m|
95
+ next if m.is_a?(String)
96
+ spec = m.specificity
97
+ if spec.is_a?(Range)
98
+ min += spec.begin
99
+ max += spec.end
100
+ else
101
+ min += spec
102
+ max += spec
103
+ end
104
+ end
105
+ min == max ? min : (min..max)
91
106
  end
92
107
  end
93
108
  end
@@ -53,20 +53,100 @@ module Sass
53
53
  # The extensions to perform on this selector
54
54
  # @param parent_directives [Array<Sass::Tree::DirectiveNode>]
55
55
  # The directives containing this selector.
56
+ # @param replace [Boolean]
57
+ # Whether to replace the original selector entirely or include
58
+ # it in the result.
59
+ # @param seen [Set<Array<Selector::Simple>>]
60
+ # The set of simple sequences that are currently being replaced.
61
+ # @param original [Boolean]
62
+ # Whether this is the original selector being extended, as opposed to
63
+ # the result of a previous extension that's being re-extended.
56
64
  # @return [CommaSequence] A copy of this selector,
57
65
  # with extensions made according to `extends`
58
- def do_extend(extends, parent_directives)
66
+ def do_extend(extends, parent_directives = [], replace = false, seen = Set.new,
67
+ original = true)
59
68
  CommaSequence.new(members.map do |seq|
60
- extended = seq.do_extend(extends, parent_directives)
61
- # First Law of Extend: the result of extending a selector should
62
- # always contain the base selector.
63
- #
64
- # See https://github.com/nex3/sass/issues/324.
65
- extended.unshift seq unless seq.has_placeholder? || extended.include?(seq)
66
- extended
69
+ seq.do_extend(extends, parent_directives, replace, seen, original)
67
70
  end.flatten)
68
71
  end
69
72
 
73
+ # Returns whether or not this selector matches all elements
74
+ # that the given selector matches (as well as possibly more).
75
+ #
76
+ # @example
77
+ # (.foo).superselector?(.foo.bar) #=> true
78
+ # (.foo).superselector?(.bar) #=> false
79
+ # @param cseq [CommaSequence]
80
+ # @return [Boolean]
81
+ def superselector?(cseq)
82
+ cseq.members.all? {|seq1| members.any? {|seq2| seq2.superselector?(seq1)}}
83
+ end
84
+
85
+ # Populates a subset map that can then be used to extend
86
+ # selectors. This registers an extension with this selector as
87
+ # the extender and `extendee` as the extendee.
88
+ #
89
+ # @param extends [Sass::Util::SubsetMap{Selector::Simple =>
90
+ # Sass::Tree::Visitors::Cssize::Extend}]
91
+ # The subset map representing the extensions to perform.
92
+ # @param extendee [CommaSequence] The selector being extended.
93
+ # @param extend_node [Sass::Tree::ExtendNode]
94
+ # The node that caused this extension.
95
+ # @param parent_directives [Array<Sass::Tree::DirectiveNode>]
96
+ # The parent directives containing `extend_node`.
97
+ # @raise [Sass::SyntaxError] if this extension is invalid.
98
+ def populate_extends(extends, extendee, extend_node = nil, parent_directives = [])
99
+ extendee.members.each do |seq|
100
+ if seq.members.size > 1
101
+ raise Sass::SyntaxError.new("Can't extend #{seq}: can't extend nested selectors")
102
+ end
103
+
104
+ sseq = seq.members.first
105
+ if !sseq.is_a?(Sass::Selector::SimpleSequence)
106
+ raise Sass::SyntaxError.new("Can't extend #{seq}: invalid selector")
107
+ elsif sseq.members.any? {|ss| ss.is_a?(Sass::Selector::Parent)}
108
+ raise Sass::SyntaxError.new("Can't extend #{seq}: can't extend parent selectors")
109
+ end
110
+
111
+ sel = sseq.members
112
+ members.each do |member|
113
+ unless member.members.last.is_a?(Sass::Selector::SimpleSequence)
114
+ raise Sass::SyntaxError.new("#{member} can't extend: invalid selector")
115
+ end
116
+
117
+ extends[sel] = Sass::Tree::Visitors::Cssize::Extend.new(
118
+ member, sel, extend_node, parent_directives, :not_found)
119
+ end
120
+ end
121
+ end
122
+
123
+ # Unifies this with another comma selector to produce a selector
124
+ # that matches (a subset of) the intersection of the two inputs.
125
+ #
126
+ # @param other [CommaSequence]
127
+ # @return [CommaSequence, nil] The unified selector, or nil if unification failed.
128
+ # @raise [Sass::SyntaxError] If this selector cannot be unified.
129
+ # This will only ever occur when a dynamic selector,
130
+ # such as {Parent} or {Interpolation}, is used in unification.
131
+ # Since these selectors should be resolved
132
+ # by the time extension and unification happen,
133
+ # this exception will only ever be raised as a result of programmer error
134
+ def unify(other)
135
+ results = members.map {|seq1| other.members.map {|seq2| seq1.unify(seq2)}}.flatten.compact
136
+ results.empty? ? nil : CommaSequence.new(results.map {|cseq| cseq.members}.flatten)
137
+ end
138
+
139
+ # Returns a SassScript representation of this selector.
140
+ #
141
+ # @return [Sass::Script::Value::List]
142
+ def to_sass_script
143
+ Sass::Script::Value::List.new(members.map do |seq|
144
+ Sass::Script::Value::List.new(seq.members.map do |component|
145
+ Sass::Script::Value::String.new(component.to_s)
146
+ end, :space)
147
+ end, :comma)
148
+ end
149
+
70
150
  # Returns a string representation of the sequence.
71
151
  # This is basically the selector string.
72
152
  #
@@ -75,10 +155,9 @@ module Sass
75
155
  members.map {|m| m.inspect}.join(", ")
76
156
  end
77
157
 
78
- # @see Simple#to_a
79
- def to_a
80
- arr = Sass::Util.intersperse(@members.map {|m| m.to_a}, ", ").flatten
81
- Sass::Util.replace_subseq(arr, [", ", "\n"], [",\n"])
158
+ # @see AbstractSequence#to_s
159
+ def to_s
160
+ @members.join(", ").gsub(", \n", ",\n")
82
161
  end
83
162
 
84
163
  private
@@ -0,0 +1,256 @@
1
+ module Sass
2
+ module Selector
3
+ # A pseudoclass (e.g. `:visited`) or pseudoelement (e.g. `::first-line`)
4
+ # selector. It can have arguments (e.g. `:nth-child(2n+1)`) which can
5
+ # contain selectors (e.g. `:nth-child(2n+1 of .foo)`).
6
+ class Pseudo < Simple
7
+ # Some pseudo-class-syntax selectors are actually considered
8
+ # pseudo-elements and must be treated differently. This is a list of such
9
+ # selectors.
10
+ #
11
+ # @return [Set<String>]
12
+ ACTUALLY_ELEMENTS = %w[after before first-line first-letter].to_set
13
+
14
+ # Like \{#type}, but returns the type of selector this looks like, rather
15
+ # than the type it is semantically. This only differs from type for
16
+ # selectors in \{ACTUALLY\_ELEMENTS}.
17
+ #
18
+ # @return [Symbol]
19
+ attr_reader :syntactic_type
20
+
21
+ # The name of the selector.
22
+ #
23
+ # @return [String]
24
+ attr_reader :name
25
+
26
+ # The argument to the selector,
27
+ # or `nil` if no argument was given.
28
+ #
29
+ # @return [String, nil]
30
+ attr_reader :arg
31
+
32
+ # The selector argument, or `nil` if no selector exists.
33
+ #
34
+ # If this and \{#arg\} are both set, \{#arg\} is considered a non-selector
35
+ # prefix.
36
+ #
37
+ # @return [CommaSequence]
38
+ attr_reader :selector
39
+
40
+ # @param syntactic_type [Symbol] See \{#syntactic_type}
41
+ # @param name [String] See \{#name}
42
+ # @param arg [nil, String] See \{#arg}
43
+ # @param selector [nil, CommaSequence] See \{#selector}
44
+ def initialize(syntactic_type, name, arg, selector)
45
+ @syntactic_type = syntactic_type
46
+ @name = name
47
+ @arg = arg
48
+ @selector = selector
49
+ end
50
+
51
+ # Returns a copy of this with \{#selector} set to \{#new\_selector}.
52
+ #
53
+ # @param new_selector [CommaSequence]
54
+ # @return [CommaSequence]
55
+ def with_selector(new_selector)
56
+ Pseudo.new(syntactic_type, name, arg, CommaSequence.new(new_selector.members.map do |seq|
57
+ next seq unless seq.members.length == 1
58
+ sseq = seq.members.first
59
+ next seq unless sseq.is_a?(SimpleSequence) && sseq.members.length == 1
60
+ sel = sseq.members.first
61
+ next seq unless sel.is_a?(Pseudo) && sel.selector
62
+
63
+ case normalized_name
64
+ when 'not'
65
+ # In theory, if there's a nested :not its contents should be
66
+ # unified with the return value. For example, if :not(.foo)
67
+ # extends .bar, :not(.bar) should become .foo:not(.bar). However,
68
+ # this is a narrow edge case and supporting it properly would make
69
+ # this code and the code calling it a lot more complicated, so
70
+ # it's not supported for now.
71
+ next [] unless sel.normalized_name == 'matches'
72
+ sel.selector.members
73
+ when 'matches', 'any', 'current', 'nth-child', 'nth-last-child'
74
+ # As above, we could theoretically support :not within :matches, but
75
+ # doing so would require this method and its callers to handle much
76
+ # more complex cases that likely aren't worth the pain.
77
+ next [] unless sel.name == name && sel.arg == arg
78
+ sel.selector.members
79
+ when 'has', 'host', 'host-context'
80
+ # We can't expand nested selectors here, because each layer adds an
81
+ # additional layer of semantics. For example, `:has(:has(img))`
82
+ # doesn't match `<div><img></div>` but `:has(img)` does.
83
+ sel
84
+ else
85
+ []
86
+ end
87
+ end.flatten))
88
+ end
89
+
90
+ # The type of the selector. `:class` if this is a pseudoclass selector,
91
+ # `:element` if it's a pseudoelement.
92
+ #
93
+ # @return [Symbol]
94
+ def type
95
+ ACTUALLY_ELEMENTS.include?(normalized_name) ? :element : syntactic_type
96
+ end
97
+
98
+ # Like \{#name\}, but without any vendor prefix.
99
+ #
100
+ # @return [String]
101
+ def normalized_name
102
+ @normalized_name ||= name.gsub(/^-[a-zA-Z0-9]+-/, '')
103
+ end
104
+
105
+ # @see Selector#to_s
106
+ def to_s
107
+ res = (syntactic_type == :class ? ":" : "::") + @name
108
+ if @arg || @selector
109
+ res << "("
110
+ res << @arg.strip if @arg
111
+ res << " " if @arg && @selector
112
+ res << @selector.to_s if @selector
113
+ res << ")"
114
+ end
115
+ res
116
+ end
117
+
118
+ # Returns `nil` if this is a pseudoelement selector
119
+ # and `sels` contains a pseudoelement selector different than this one.
120
+ #
121
+ # @see SimpleSequence#unify
122
+ def unify(sels)
123
+ return if type == :element && sels.any? do |sel|
124
+ sel.is_a?(Pseudo) && sel.type == :element &&
125
+ (sel.name != name || sel.arg != arg || sel.selector != selector)
126
+ end
127
+ super
128
+ end
129
+
130
+ # Returns whether or not this selector matches all elements
131
+ # that the given selector matches (as well as possibly more).
132
+ #
133
+ # @example
134
+ # (.foo).superselector?(.foo.bar) #=> true
135
+ # (.foo).superselector?(.bar) #=> false
136
+ # @param their_sseq [SimpleSequence]
137
+ # @param parents [Array<SimpleSequence, String>] The parent selectors of `their_sseq`, if any.
138
+ # @return [Boolean]
139
+ def superselector?(their_sseq, parents = [])
140
+ case normalized_name
141
+ when 'matches', 'any'
142
+ # :matches can be a superselector of another selector in one of two
143
+ # ways. Either its constituent selectors can be a superset of those of
144
+ # another :matches in the other selector, or any of its constituent
145
+ # selectors can individually be a superselector of the other selector.
146
+ (their_sseq.selector_pseudo_classes[normalized_name] || []).any? do |their_sel|
147
+ next false unless their_sel.is_a?(Pseudo)
148
+ next false unless their_sel.name == name
149
+ selector.superselector?(their_sel.selector)
150
+ end || selector.members.any? do |our_seq|
151
+ their_seq = Sequence.new(parents + [their_sseq])
152
+ our_seq.superselector?(their_seq)
153
+ end
154
+ when 'has', 'host', 'host-context'
155
+ # Like :matches, :has (et al) can be a superselector of another
156
+ # selector if its constituent selectors are a superset of those of
157
+ # another :has in the other selector. However, the :matches other case
158
+ # doesn't work, because :has refers to nested elements.
159
+ (their_sseq.selector_pseudo_classes[normalized_name] || []).any? do |their_sel|
160
+ next false unless their_sel.is_a?(Pseudo)
161
+ next false unless their_sel.name == name
162
+ selector.superselector?(their_sel.selector)
163
+ end
164
+ when 'not'
165
+ selector.members.all? do |our_seq|
166
+ their_sseq.members.any? do |their_sel|
167
+ if their_sel.is_a?(Element) || their_sel.is_a?(Id)
168
+ # `:not(a)` is a superselector of `h1` and `:not(#foo)` is a
169
+ # superselector of `#bar`.
170
+ our_sseq = our_seq.members.last
171
+ next false unless our_sseq.is_a?(SimpleSequence)
172
+ our_sseq.members.any? do |our_sel|
173
+ our_sel.class == their_sel.class && our_sel != their_sel
174
+ end
175
+ else
176
+ next false unless their_sel.is_a?(Pseudo)
177
+ next false unless their_sel.name == name
178
+ # :not(X) is a superselector of :not(Y) exactly when Y is a
179
+ # superselector of X.
180
+ their_sel.selector.superselector?(CommaSequence.new([our_seq]))
181
+ end
182
+ end
183
+ end
184
+ when 'current'
185
+ (their_sseq.selector_pseudo_classes['current'] || []).any? do |their_current|
186
+ next false if their_current.name != name
187
+ # Explicitly don't check for nested superselector relationships
188
+ # here. :current(.foo) isn't always a superselector of
189
+ # :current(.foo.bar), since it matches the *innermost* ancestor of
190
+ # the current element that matches the selector. For example:
191
+ #
192
+ # <div class="foo bar">
193
+ # <p class="foo">
194
+ # <span>current element</span>
195
+ # </p>
196
+ # </div>
197
+ #
198
+ # Here :current(.foo) would match the p element and *not* the div
199
+ # element, whereas :current(.foo.bar) would match the div and not
200
+ # the p.
201
+ selector == their_current.selector
202
+ end
203
+ when 'nth-child', 'nth-last-child'
204
+ their_sseq.members.any? do |their_sel|
205
+ # This misses a few edge cases. For example, `:nth-child(n of X)`
206
+ # is a superselector of `X`, and `:nth-child(2n of X)` is a
207
+ # superselector of `:nth-child(4n of X)`. These seem rare enough
208
+ # not to be worth worrying about, though.
209
+ next false unless their_sel.is_a?(Pseudo)
210
+ next false unless their_sel.name == name
211
+ next false unless their_sel.arg == arg
212
+ selector.superselector?(their_sel.selector)
213
+ end
214
+ else
215
+ throw "[BUG] Unknown selector pseudo class #{name}"
216
+ end
217
+ end
218
+
219
+ # @see AbstractSequence#specificity
220
+ def specificity
221
+ return 1 if type == :element
222
+ return SPECIFICITY_BASE unless selector
223
+ @specificity ||=
224
+ if normalized_name == 'not'
225
+ min = 0
226
+ max = 0
227
+ selector.members.each do |seq|
228
+ spec = seq.specificity
229
+ if spec.is_a?(Range)
230
+ min = Sass::Util.max(spec.begin, min)
231
+ max = Sass::Util.max(spec.end, max)
232
+ else
233
+ min = Sass::Util.max(spec, min)
234
+ max = Sass::Util.max(spec, max)
235
+ end
236
+ end
237
+ min == max ? max : (min..max)
238
+ else
239
+ min = 0
240
+ max = 0
241
+ selector.members.each do |seq|
242
+ spec = seq.specificity
243
+ if spec.is_a?(Range)
244
+ min = Sass::Util.min(spec.begin, min)
245
+ max = Sass::Util.max(spec.end, max)
246
+ else
247
+ min = Sass::Util.min(spec, min)
248
+ max = Sass::Util.max(spec, max)
249
+ end
250
+ end
251
+ min == max ? max : (min..max)
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
@@ -78,50 +78,76 @@ module Sass
78
78
  # Non-destructively extends this selector with the extensions specified in a hash
79
79
  # (which should come from {Sass::Tree::Visitors::Cssize}).
80
80
  #
81
- # @overload do_extend(extends, parent_directives)
82
- # @param extends [Sass::Util::SubsetMap{Selector::Simple =>
83
- # Sass::Tree::Visitors::Cssize::Extend}]
84
- # The extensions to perform on this selector
85
- # @param parent_directives [Array<Sass::Tree::DirectiveNode>]
86
- # The directives containing this selector.
81
+ # @param extends [Sass::Util::SubsetMap{Selector::Simple =>
82
+ # Sass::Tree::Visitors::Cssize::Extend}]
83
+ # The extensions to perform on this selector
84
+ # @param parent_directives [Array<Sass::Tree::DirectiveNode>]
85
+ # The directives containing this selector.
86
+ # @param replace [Boolean]
87
+ # Whether to replace the original selector entirely or include
88
+ # it in the result.
89
+ # @param seen [Set<Array<Selector::Simple>>]
90
+ # The set of simple sequences that are currently being replaced.
91
+ # @param original [Boolean]
92
+ # Whether this is the original selector being extended, as opposed to
93
+ # the result of a previous extension that's being re-extended.
87
94
  # @return [Array<Sequence>] A list of selectors generated
88
95
  # by extending this selector with `extends`.
89
96
  # These correspond to a {CommaSequence}'s {CommaSequence#members members array}.
90
97
  # @see CommaSequence#do_extend
91
- def do_extend(extends, parent_directives, seen = Set.new)
98
+ def do_extend(extends, parent_directives, replace, seen, original)
92
99
  extended_not_expanded = members.map do |sseq_or_op|
93
100
  next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence)
94
- extended = sseq_or_op.do_extend(extends, parent_directives, seen)
95
- choices = extended.map {|seq| seq.members}
96
- choices.unshift([sseq_or_op]) unless extended.any? {|seq| seq.superselector?(sseq_or_op)}
97
- choices
101
+ extended = sseq_or_op.do_extend(extends, parent_directives, replace, seen)
102
+
103
+ # The First Law of Extend says that the generated selector should have
104
+ # specificity greater than or equal to that of the original selector.
105
+ # In order to ensure that, we record the original selector's
106
+ # (`extended.first`) original specificity.
107
+ extended.first.add_sources!([self]) if original && !has_placeholder?
108
+
109
+ extended.map {|seq| seq.members}
98
110
  end
99
111
  weaves = Sass::Util.paths(extended_not_expanded).map {|path| weave(path)}
100
112
  trim(weaves).map {|p| Sequence.new(p)}
101
113
  end
102
114
 
115
+ # Unifies this with another selector sequence to produce a selector
116
+ # that matches (a subset of) the intersection of the two inputs.
117
+ #
118
+ # @param other [Sequence]
119
+ # @return [CommaSequence, nil] The unified selector, or nil if unification failed.
120
+ # @raise [Sass::SyntaxError] If this selector cannot be unified.
121
+ # This will only ever occur when a dynamic selector,
122
+ # such as {Parent} or {Interpolation}, is used in unification.
123
+ # Since these selectors should be resolved
124
+ # by the time extension and unification happen,
125
+ # this exception will only ever be raised as a result of programmer error
126
+ def unify(other)
127
+ base = members.last
128
+ other_base = other.members.last
129
+ return unless base.is_a?(SimpleSequence) && other_base.is_a?(SimpleSequence)
130
+ return unless (unified = other_base.unify(base))
131
+
132
+ woven = weave([members[0...-1], other.members[0...-1] + [unified]])
133
+ CommaSequence.new(woven.map {|w| Sequence.new(w)})
134
+ end
135
+
103
136
  # Returns whether or not this selector matches all elements
104
137
  # that the given selector matches (as well as possibly more).
105
138
  #
106
139
  # @example
107
140
  # (.foo).superselector?(.foo.bar) #=> true
108
141
  # (.foo).superselector?(.bar) #=> false
109
- # (.bar .foo).superselector?(.foo) #=> false
110
- # @param sseq [SimpleSequence]
142
+ # @param cseq [Sequence]
111
143
  # @return [Boolean]
112
- def superselector?(sseq)
113
- return false unless members.size == 1
114
- members.last.superselector?(sseq)
144
+ def superselector?(seq)
145
+ _superselector?(members, seq.members)
115
146
  end
116
147
 
117
- # @see Simple#to_a
118
- def to_a
119
- ary = @members.map do |seq_or_op|
120
- seq_or_op.is_a?(SimpleSequence) ? seq_or_op.to_a : seq_or_op
121
- end
122
- ary = Sass::Util.intersperse(ary, " ").flatten.compact
123
- ary = Sass::Util.replace_subseq(ary, ["\n", " "], ["\n"])
124
- Sass::Util.replace_subseq(ary, [" ", "\n"], ["\n"])
148
+ # @see AbstractSequence#to_s
149
+ def to_s
150
+ @members.join(" ").gsub(/ ?\n ?/, "\n")
125
151
  end
126
152
 
127
153
  # Returns a string representation of the sequence.
@@ -141,6 +167,35 @@ module Sass
141
167
  members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m}
142
168
  end
143
169
 
170
+ # Converts the subject operator "!", if it exists, into a ":has()"
171
+ # selector.
172
+ #
173
+ # @retur [Sequence]
174
+ def subjectless
175
+ pre_subject = []
176
+ has = []
177
+ subject = nil
178
+ members.each do |sseq_or_op|
179
+ if subject
180
+ has << sseq_or_op
181
+ elsif sseq_or_op.is_a?(String) || !sseq_or_op.subject?
182
+ pre_subject << sseq_or_op
183
+ else
184
+ subject = sseq_or_op.dup
185
+ subject.members = sseq_or_op.members.dup
186
+ subject.subject = false
187
+ has = []
188
+ end
189
+ end
190
+
191
+ return self unless subject
192
+
193
+ unless has.empty?
194
+ subject.members << Pseudo.new(:class, 'has', nil, CommaSequence.new([Sequence.new(has)]))
195
+ end
196
+ Sequence.new(pre_subject + [subject])
197
+ end
198
+
144
199
  private
145
200
 
146
201
  # Conceptually, this expands "parenthesized selectors". That is, if we
@@ -159,6 +214,7 @@ module Sass
159
214
  prefixes = [[]]
160
215
 
161
216
  path.each do |current|
217
+ next if current.empty?
162
218
  current = current.dup
163
219
  last_current = [current.pop]
164
220
  prefixes = Sass::Util.flatten(prefixes.map do |prefix|
@@ -292,7 +348,7 @@ module Sass
292
348
  elsif sel2.superselector?(sel1)
293
349
  res.unshift sel1, '~'
294
350
  else
295
- merged = sel1.unify(sel2.members, sel2.subject?)
351
+ merged = sel1.unify(sel2)
296
352
  res.unshift [
297
353
  [sel1, '~', sel2, '~'],
298
354
  [sel2, '~', sel1, '~'],
@@ -309,7 +365,7 @@ module Sass
309
365
  if tilde_sel.superselector?(plus_sel)
310
366
  res.unshift plus_sel, '+'
311
367
  else
312
- merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?)
368
+ merged = plus_sel.unify(tilde_sel)
313
369
  res.unshift [
314
370
  [tilde_sel, '~', plus_sel, '+'],
315
371
  ([merged, '+'] if merged)
@@ -322,7 +378,7 @@ module Sass
322
378
  res.unshift sel1, op1
323
379
  seq2.push sel2, op2
324
380
  elsif op1 == op2
325
- merged = sel1.unify(sel2.members, sel2.subject?)
381
+ merged = sel1.unify(sel2)
326
382
  return unless merged
327
383
  res.unshift merged, op1
328
384
  else
@@ -412,12 +468,12 @@ module Sass
412
468
  seq1.first.is_a?(String) || seq2.first.is_a?(String)
413
469
  # More complex selectors are never superselectors of less complex ones
414
470
  return if seq1.size > seq2.size
415
- return seq1.first.superselector?(seq2.last) if seq1.size == 1
471
+ return seq1.first.superselector?(seq2.last, seq2[0...-1]) if seq1.size == 1
416
472
 
417
473
  _, si = Sass::Util.enum_with_index(seq2).find do |e, i|
418
474
  return if i == seq2.size - 1
419
475
  next if e.is_a?(String)
420
- seq1.first.superselector?(e)
476
+ seq1.first.superselector?(e, seq2[0...i])
421
477
  end
422
478
  return unless si
423
479
 
@@ -475,7 +531,15 @@ module Sass
475
531
  # separate sequences should limit the quadratic behavior.
476
532
  seqses.each_with_index do |seqs1, i|
477
533
  result[i] = seqs1.reject do |seq1|
478
- max_spec = _sources(seq1).map {|seq| seq.specificity}.max || 0
534
+ # The maximum specificity of the sources that caused [seq1] to be
535
+ # generated. In order for [seq1] to be removed, there must be
536
+ # another selector that's a superselector of it *and* that has
537
+ # specificity greater or equal to this.
538
+ max_spec = _sources(seq1).map do |seq|
539
+ spec = seq.specificity
540
+ spec.is_a?(Range) ? spec.max : spec
541
+ end.max || 0
542
+
479
543
  result.any? do |seqs2|
480
544
  next if seqs1.equal?(seqs2)
481
545
  # Second Law of Extend: the specificity of a generated selector
@@ -483,7 +547,11 @@ module Sass
483
547
  # selector.
484
548
  #
485
549
  # See https://github.com/nex3/sass/issues/324.
486
- seqs2.any? {|seq2| _specificity(seq2) >= max_spec && _superselector?(seq2, seq1)}
550
+ seqs2.any? do |seq2|
551
+ spec2 = _specificity(seq2)
552
+ spec2 = spec2.begin if spec2.is_a?(Range)
553
+ spec2 >= max_spec && _superselector?(seq2, seq1)
554
+ end
487
555
  end
488
556
  end
489
557
  end