sass 3.3.14 → 3.4.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 (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