Skip to content

Commit

Permalink
Deprecate the subject selector operator.
Browse files Browse the repository at this point in the history
Closes #1126
  • Loading branch information
nex3 committed Jul 12, 2014
1 parent 68e9035 commit fe6b984
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 41 deletions.
3 changes: 3 additions & 0 deletions doc-src/SASS_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ adding multiple suffixes to the same parent selector. For example:
invalid according to the CSS string grammar. To include a newline in a string,
use "\a" or "\a " as in CSS.

* The subject selector operator, `!`, is deprecated and will produce a warning
if used. The `:has()` selector should be used instead.

## 3.3.10 (Unreleased)

* Properly encode URLs in sourcemaps.
Expand Down
25 changes: 2 additions & 23 deletions lib/sass/scss/css_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,29 +31,8 @@ def nested_properties!(node)

def ruleset
start_pos = source_position
rules, source_range = selector_sequence
return unless rules
block(node(Sass::Tree::RuleNode.new([rules], source_range), start_pos), :ruleset)
end

def selector_sequence
start_pos = source_position
if (sel = tok(STATIC_SELECTOR, true))
return sel, range(start_pos)
end

sel = selector_string
return unless sel

ws = ''
while tok(/,/)
ws << str {ss}
if (v = selector_string)
sel << ',' << ws << v
ws = ''
end
end
return sel, range(start_pos)
return unless (selector = selector_comma_sequence)
block(node(Sass::Tree::RuleNode.new(selector, range(start_pos)), start_pos), :ruleset)
end

@sass_script_parser = Class.new(Sass::Script::CssParser)
Expand Down
15 changes: 14 additions & 1 deletion lib/sass/scss/static_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def selector_string
end

def selector
start_pos = source_position
# The combinator here allows the "> E" hack
val = combinator || simple_selector_sequence
return unless val
Expand All @@ -119,7 +120,19 @@ def selector
res << val
res << "\n" if str {ss}.include?("\n")
end
Selector::Sequence.new(res.compact)
seq = Selector::Sequence.new(res.compact)

if seq.members.any? {|sseq| sseq.is_a?(Selector::SimpleSequence) && sseq.subject?}
location = " of #{@filename}" if @filename
Sass::Util.sass_warn <<MESSAGE
DEPRECATION WARNING on line #{start_pos.line}, column #{start_pos.offset}#{location}:
The subject selector operator "!" is deprecated and will be removed in a future release.
This operator has been replaced by ":has()" in the CSS spec.
For example: #{seq.subjectless}
MESSAGE
end

seq
end

def combinator
Expand Down
29 changes: 29 additions & 0 deletions lib/sass/selector/sequence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,35 @@ def add_sources!(sources)
members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m}
end

# Converts the subject operator "!", if it exists, into a ":has()"
# selector.
#
# @retur [Sequence]
def subjectless
pre_subject = []
has = []
subject = nil
members.each do |sseq_or_op|
if subject
has << sseq_or_op
elsif sseq_or_op.is_a?(String) || !sseq_or_op.subject?
pre_subject << sseq_or_op
else
subject = sseq_or_op.dup
subject.members = sseq_or_op.members.dup
subject.subject = false
has = []
end
end

return self unless subject

unless has.empty?
subject.members << Pseudo.new(:class, 'has', nil, CommaSequence.new([Sequence.new(has)]))
end
Sequence.new(pre_subject + [subject])
end

private

# Conceptually, this expands "parenthesized selectors". That is, if we
Expand Down
4 changes: 3 additions & 1 deletion lib/sass/selector/simple_sequence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,9 @@ def to_s
#
# @return [String]
def inspect
members.map {|m| m.inspect}.join
res = members.map {|m| m.inspect}.join
res << '!' if subject?
res
end

# Return a copy of this simple sequence with `sources` merged into the
Expand Down
17 changes: 11 additions & 6 deletions lib/sass/tree/rule_node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,20 @@ class RuleNode < Node
# @return [String]
attr_accessor :stack_trace

# @param rule [Array<String, Sass::Script::Tree::Node>]
# @param rule [Array<String, Sass::Script::Tree::Node>, Sass::Selector::CommaSequence]
# The CSS rule, either unparsed or parsed.
# @param selector_source_range [Sass::Source::Range]
# The CSS rule. See \{#rule}
def initialize(rule, selector_source_range = nil)
merged = Sass::Util.merge_adjacent_strings(rule)
@rule = Sass::Util.strip_string_array(merged)
if rule.is_a?(Sass::Selector::CommaSequence)
@rule = [rule.to_s]
@parsed_rules = rule
else
merged = Sass::Util.merge_adjacent_strings(rule)
@rule = Sass::Util.strip_string_array(merged)
try_to_parse_non_interpolated_rules
end
@selector_source_range = selector_source_range
@tabs = 0
try_to_parse_non_interpolated_rules
super()
end

Expand Down Expand Up @@ -130,7 +135,7 @@ def try_to_parse_non_interpolated_rules
if @rule.all? {|t| t.kind_of?(String)}
# We don't use real filename/line info because we don't have it yet.
# When we get it, we'll set it on the parsed rules if possible.
parser = Sass::SCSS::StaticParser.new(@rule.join.strip, '', nil, 1)
parser = Sass::SCSS::StaticParser.new(@rule.join.strip, nil, nil, 1)
# rubocop:disable RescueModifier
@parsed_rules = parser.parse_selector rescue nil
# rubocop:enable RescueModifier
Expand Down
2 changes: 1 addition & 1 deletion test/sass/css2sass_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ def test_pseudo_classes_are_escaped
end

def test_subject
assert_equal(<<SASS, css2sass(<<CSS))
silence_warnings {assert_equal(<<SASS, css2sass(<<CSS))}
.foo
.bar!
.baz
Expand Down
12 changes: 6 additions & 6 deletions test/sass/extend_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1202,15 +1202,15 @@ def test_extend_within_and_without_nested_directives
end

def test_extend_with_subject_transfers_subject_to_extender
assert_equal(<<CSS, render(<<SCSS))
silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
foo bar! baz, foo .bip .bap! baz, .bip foo .bap! baz {
a: b; }
CSS
foo bar! baz {a: b}
.bip .bap {@extend bar}
SCSS

assert_equal(<<CSS, render(<<SCSS))
silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
foo.x bar.y! baz.z, foo.x .bip bar.bap! baz.z, .bip foo.x bar.bap! baz.z {
a: b; }
CSS
Expand All @@ -1220,7 +1220,7 @@ def test_extend_with_subject_transfers_subject_to_extender
end

def test_extend_with_subject_retains_subject_on_target
assert_equal(<<CSS, render(<<SCSS))
silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
.foo! .bar, .foo! .bip .bap, .bip .foo! .bap {
a: b; }
CSS
Expand All @@ -1230,7 +1230,7 @@ def test_extend_with_subject_retains_subject_on_target
end

def test_extend_with_subject_transfers_subject_to_target
assert_equal(<<CSS, render(<<SCSS))
silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
a.foo .bar, .bip a.bap! .bar {
a: b; }
CSS
Expand All @@ -1240,7 +1240,7 @@ def test_extend_with_subject_transfers_subject_to_target
end

def test_extend_with_subject_retains_subject_on_extender
assert_equal(<<CSS, render(<<SCSS))
silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
.foo .bar, .foo .bip! .bap, .bip! .foo .bap {
a: b; }
CSS
Expand All @@ -1250,7 +1250,7 @@ def test_extend_with_subject_retains_subject_on_extender
end

def test_extend_with_subject_fails_with_conflicting_subject
assert_equal(<<CSS, render(<<SCSS))
silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
x! .bar {
a: b; }
CSS
Expand Down
25 changes: 24 additions & 1 deletion test/sass/scss/css_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ def test_summarized_selectors_with_element
assert_selector_parses('E + F')
assert_selector_parses('E ~ F')
assert_selector_parses('E /foo/ F')
assert_selector_parses('E! > F')
silence_warnings {assert_selector_parses('E! > F')}

assert_selector_parses('E /ns|foo/ F')

Expand Down Expand Up @@ -978,6 +978,29 @@ def test_spaceless_combo_selectors
assert_equal "E + F {\n a: b; }\n", render("E+F { a: b;} ")
end

def test_subject_selector_deprecation
assert_warning(<<WARNING) {render(".foo .bar! .baz {a: b}")}
DEPRECATION WARNING on line 1, column 1:
The subject selector operator "!" is deprecated and will be removed in a future release.
This operator has been replaced by ":has()" in the CSS spec.
For example: .foo .bar:has(.baz)
WARNING

assert_warning(<<WARNING) {render(".foo .bar! > .baz {a: b}")}
DEPRECATION WARNING on line 1, column 1:
The subject selector operator "!" is deprecated and will be removed in a future release.
This operator has been replaced by ":has()" in the CSS spec.
For example: .foo .bar:has(> .baz)
WARNING

assert_warning(<<WARNING) {render(".foo .bar! {a: b}")}
DEPRECATION WARNING on line 1, column 1:
The subject selector operator "!" is deprecated and will be removed in a future release.
This operator has been replaced by ":has()" in the CSS spec.
For example: .foo .bar
WARNING
end

## Errors

def test_invalid_directives
Expand Down
4 changes: 2 additions & 2 deletions test/sass/scss/scss_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ def test_parent_selectors
end

def test_parent_selector_with_subject
assert_equal <<CSS, render(<<SCSS)
silence_warnings {assert_equal <<CSS, render(<<SCSS)}
bar foo.baz! .bip {
a: b; }
Expand Down Expand Up @@ -2055,7 +2055,7 @@ def test_selector_interpolation_in_reference_combinator
end

def test_parent_selector_with_parent_and_subject
assert_equal <<CSS, render(<<SCSS)
silence_warnings {assert_equal <<CSS, render(<<SCSS)}
bar foo.baz! .bip {
c: d; }
CSS
Expand Down

0 comments on commit fe6b984

Please sign in to comment.