Permalink
Browse files

Support &-suffix.

Closes #1055.
  • Loading branch information...
1 parent 05fc8e2 commit 3f1498a55d89f50c343368dbd012313bf9c5ae16 @nex3 nex3 committed Jan 22, 2014
View
6 doc-src/SASS_CHANGELOG.md
@@ -229,6 +229,12 @@ maps instead.
### Smaller Improvements
+* The parent selector, `&`, can be used with an identifier suffix. For
+ example, `&-suffix` and `&_suffix` are now legal. The suffix will be
+ added to the end of the parent selector, and will throw an error if
+ this isn't possible. `&` must still appear at the beginning of a
+ compound selector -- that is, `.foo-&` is still illegal.
+
* [listen](http://github.com/guard/listen) is now a standard Gem dependency.
It's no longer bundled with Sass.
View
19 doc-src/SASS_REFERENCE.md
@@ -493,6 +493,25 @@ is compiled to:
#main a:hover {
color: red; }
+`&` must appear at the beginning of a compound selector, but it can be
+followed by a suffix that will be added to the parent selector. For
+example:
+
+ #main {
+ color: black;
+ &-sidebar { border: 1px solid; }
+ }
+
+is compiled to:
+
+ #main {
+ color: black; }
+ #main-sidebar {
+ border: 1px solid; }
+
+If the parent selector can't have a suffix applied, Sass will throw an
+error.
+
### Nested Properties
CSS has quite a few properties that are in "namespaces;"
View
2 lib/sass/scss/parser.rb
@@ -813,7 +813,7 @@ def simple_selector_sequence
def parent_selector
return unless tok(/&/)
- Selector::Parent.new
+ Selector::Parent.new(interp_ident(NAME) || [])
end
def class_selector
View
12 lib/sass/selector.rb
@@ -27,9 +27,19 @@ module Selector
# The function of this is to be replaced by the parent selector
# in the nested hierarchy.
class Parent < Simple
+ # The identifier following the `&`. Often empty.
+ #
+ # @return [Array<String, Sass::Script::Tree::Node>]
+ attr_reader :suffix
+
+ # @param name [Array<String, Sass::Script::Tree::Node>] See \{#suffix}
+ def initialize(suffix = [])
+ @suffix = suffix
+ end
+
# @see Selector#to_a
def to_a
- ["&"]
+ ["&", *@suffix]
end
# Always raises an exception.
View
36 lib/sass/selector/simple_sequence.rb
@@ -83,15 +83,41 @@ def initialize(selectors, subject, source_range = nil)
# @raise [Sass::SyntaxError] If a parent selector is invalid
def resolve_parent_refs(super_seq)
# Parent selector only appears as the first selector in the sequence
- return [self] unless @members.first.is_a?(Parent)
+ return [self] unless (parent = @members.first).is_a?(Parent)
- return super_seq.members if @members.size == 1
+ return super_seq.members if @members.size == 1 && parent.suffix.empty?
unless super_seq.members.last.is_a?(SimpleSequence)
- raise Sass::SyntaxError.new("Invalid parent selector: " + super_seq.to_a.join)
+ raise Sass::SyntaxError.new("Invalid parent selector for \"#{self}\": \"" +
+ super_seq.to_a.join + '"')
end
- super_seq.members[0...-1] +
- [SimpleSequence.new(super_seq.members.last.members + @members[1..-1], subject?)]
+ parent_sub = super_seq.members.last.members
+ unless parent.suffix.empty?
+ parent_sub = parent_sub.dup
+ parent_sub[-1] = parent_sub.last.dup
+ case parent_sub.last
+ when Sass::Selector::Class, Sass::Selector::Id, Sass::Selector::Placeholder
+ parent_sub[-1] = parent_sub.last.class.new(parent_sub.last.name + parent.suffix)
+ when Sass::Selector::Element
+ parent_sub[-1] = parent_sub.last.class.new(
+ parent_sub.last.name + parent.suffix,
+ parent_sub.last.namespace)
+ when Sass::Selector::Pseudo
+ if parent_sub.last.arg
+ raise Sass::SyntaxError.new("Invalid parent selector for \"#{self}\": \"" +
+ super_seq.to_a.join + '"')
+ end
+ parent_sub[-1] = parent_sub.last.class.new(
+ parent_sub.last.type,
+ parent_sub.last.name + parent.suffix,
+ nil)
+ else
+ raise Sass::SyntaxError.new("Invalid parent selector for \"#{self}\": \"" +
+ super_seq.to_a.join + '"')
+ end
+ end
+
+ super_seq.members[0...-1] + [SimpleSequence.new(parent_sub + @members[1..-1], subject?)]
end
# Non-destrucively extends this selector with the extensions specified in a hash
View
2 test/sass/engine_test.rb
@@ -158,8 +158,6 @@ class SassEngineTest < Test::Unit::TestCase
"$var: true\n@while $var\n @extend .bar\n $var: false" => ["Extend directives may only be used within rules.", 3],
"@for $i from 0 to 1\n @extend .bar" => ["Extend directives may only be used within rules.", 2],
"@mixin foo\n @extend .bar\n@include foo" => ["Extend directives may only be used within rules.", 2],
- "foo\n &a\n b: c" => ["Invalid CSS after \"&\": expected \"{\", was \"a\"\n\n\"a\" may only be used at the beginning of a compound selector.", 2],
- "foo\n &1\n b: c" => ["Invalid CSS after \"&\": expected \"{\", was \"1\"\n\n\"1\" may only be used at the beginning of a compound selector.", 2],
"foo %\n a: b" => ['Invalid CSS after "foo %": expected placeholder name, was ""', 1],
"=foo\n @content error" => "Invalid content directive. Trailing characters found: \"error\".",
"=foo\n @content\n b: c" => "Illegal nesting: Nothing may be nested beneath @content directives.",
View
69 test/sass/scss/scss_test.rb
@@ -622,6 +622,33 @@ def test_parent_selector_with_subject
SCSS
end
+ def test_parent_selector_with_suffix
+ assert_equal <<CSS, render(<<SCSS)
+.foo-bar {
+ a: b; }
+.foo_bar {
+ c: d; }
+.foobar {
+ e: f; }
+.foo123 {
+ e: f; }
+
+:hover-suffix {
+ g: h; }
+CSS
+.foo {
+ &-bar {a: b}
+ &_bar {c: d}
+ &bar {e: f}
+ &123 {e: f}
+}
+
+:hover {
+ &-suffix {g: h}
+}
+SCSS
+ end
+
def test_unknown_directive_bubbling
assert_equal(<<CSS, render(<<SCSS, :style => :nested))
@fblthp {
@@ -2988,6 +3015,48 @@ def test_no_lonely_else
SCSS
end
+ def test_failed_parent_selector_with_suffix
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)}
+Invalid parent selector for "&-bar": "*"
+MESSAGE
+* {
+ &-bar {a: b}
+}
+SCSS
+
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)}
+Invalid parent selector for "&-bar": "[foo=bar]"
+MESSAGE
+[foo=bar] {
+ &-bar {a: b}
+}
+SCSS
+
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)}
+Invalid parent selector for "&-bar": "::nth-child(2n+1)"
+MESSAGE
+::nth-child(2n+1) {
+ &-bar {a: b}
+}
+SCSS
+
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)}
+Invalid parent selector for "&-bar": ":not(.foo)"
+MESSAGE
+:not(.foo) {
+ &-bar {a: b}
+}
+SCSS
+
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)}
+Invalid parent selector for "&-bar": ".foo +"
+MESSAGE
+.foo + {
+ &-bar {a: b}
+}
+SCSS
+ end
+
# Regression
def test_loud_comment_in_compressed_mode

0 comments on commit 3f1498a

Please sign in to comment.