class Sass::SCSS::Parser
The parser for SCSS. It parses a string of code into a tree of {Sass::Tree::Node}s.
Constants
- DIRECTIVES
- PREFIXED_DIRECTIVES
Attributes
Expose for the SASS parser.
Public Class Methods
@param str [String, StringScanner] The source document to parse.
Note that `Parser` *won't* raise a nice error message if this isn't properly parsed; for that, you should use the higher-level {Sass::Engine} or {Sass::CSS}.
@param filename [String] The name of the file being parsed. Used for
warnings and source maps.
@param importer [Sass::Importers::Base] The importer used to import the
file being parsed. Used for source maps.
@param line [Fixnum] The 1-based line on which the source string appeared,
if it's part of another document.
@param offset [Fixnum] The 1-based character (not byte) offset in the line on
which the source string starts. Used for error reporting and sourcemap building.
@comment
rubocop:disable ParameterLists
# File lib/sass/scss/parser.rb, line 25 def initialize(str, filename, importer, line = 1, offset = 1) # rubocop:enable ParameterLists @template = str @filename = filename @importer = importer @line = line @offset = offset @strs = [] end
Public Instance Methods
Parses an SCSS document.
@return [Sass::Tree::RootNode] The root node of the document tree @raise [Sass::SyntaxError] if there's a syntax error in the document
# File lib/sass/scss/parser.rb, line 39 def parse init_scanner! root = stylesheet expected("selector or at-rule") unless root && @scanner.eos? root end
Parses an at-root query.
@return [Array<String, Sass::Script;:Tree::Node>] The interpolated query. @raise [Sass::SyntaxError] if there's a syntax error in the query,
or if it doesn't take up the entire input string.
# File lib/sass/scss/parser.rb, line 83 def parse_at_root_query init_scanner! query = at_root_query expected("@at-root query list") unless query && @scanner.eos? query end
Parses an identifier with interpolation. Note that this won't assert that the identifier takes up the entire input string; it's meant to be used with `StringScanner`s as part of other parsers.
@return [Array<String, Sass::Script::Tree::Node>, nil]
The interpolated identifier, or nil if none could be parsed
# File lib/sass/scss/parser.rb, line 52 def parse_interp_ident init_scanner! interp_ident end
Parses a media query list.
@return [Sass::Media::QueryList] The parsed query list @raise [Sass::SyntaxError] if there's a syntax error in the query list,
or if it doesn't take up the entire input string.
# File lib/sass/scss/parser.rb, line 71 def parse_media_query_list init_scanner! ql = media_query_list expected("media query list") unless ql && @scanner.eos? ql end
Parses a supports clause for an @import directive
# File lib/sass/scss/parser.rb, line 58 def parse_supports_clause init_scanner! ss clause = supports_clause ss clause end
Parses a supports query condition.
@return [Sass::Supports::Condition] The parsed condition @raise [Sass::SyntaxError] if there's a syntax error in the condition,
or if it doesn't take up the entire input string.
# File lib/sass/scss/parser.rb, line 95 def parse_supports_condition init_scanner! condition = supports_condition expected("supports condition") unless condition && @scanner.eos? condition end
Private Instance Methods
The document directive is specified in www.w3.org/TR/css3-conditional/, but Gecko allows the `url-prefix` and `domain` functions to omit quotation marks, contrary to the standard.
We could parse all document directives according to Mozilla's syntax, but if someone's using e.g. @-webkit-document we don't want them to think WebKit works sans quotes.
# File lib/sass/scss/parser.rb, line 498 def _moz_document_directive(start_pos) res = ["@-moz-document "] loop do res << str {ss} << expr!(:moz_document_function) if (c = tok(/,/)) res << c else break end end directive_body(res.flatten, start_pos) end
This production is similar to the CSS [`<any-value>`] production, but as the name implies, not quite the same. It's meant to consume values that could be a selector, an expression, or a combination of both. It respects strings and comments and supports interpolation. It will consume up to “{”, “}”, “;”, or “!”.
[any-value]: dev.w3.org/csswg/css-variables/#typedef-any-value
Values consumed by this production will usually be parsed more thoroughly once interpolation has been resolved.
# File lib/sass/scss/parser.rb, line 812 def almost_any_value return unless (tok = almost_any_value_token) sel = [tok] while (tok = almost_any_value_token) sel << tok end merge(sel) end
# File lib/sass/scss/parser.rb, line 821 def almost_any_value_token toktok(%r{ ( \. | (?!url\() [^"'/\#!;\{\}] # " | /(?![/*]) | \#(?!\{) | !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed. )+ }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri || interpolation(:warn_for_color) end def declaration # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop: # val" hacks. name_start_pos = source_position if (s = tok(/[:\*\.]|\#(?!\{)/)) name = [s, str {ss}, *expr!(:interp_ident)] else return unless (name = interp_ident) name = Array(name) end if (comment = tok(COMMENT)) name << comment end name_end_pos = source_position ss tok!(/:/) ss value_start_pos = source_position value = value! value_end_pos = source_position ss require_block = tok?(/\{/) node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new), name_start_pos, value_end_pos) node.name_source_range = range(name_start_pos, name_end_pos) node.value_source_range = range(value_start_pos, value_end_pos) return node unless require_block nested_properties! node end def value! if tok?(/\{/) str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new("")) str.line = source_position.line str.source_range = range(source_position) return str end start_pos = source_position # This is a bit of a dirty trick: # if the value is completely static, # we don't parse it at all, and instead return a plain old string # containing the value. # This results in a dramatic speed increase. if (val = tok(STATIC_VALUE, true)) str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(val.strip)) str.line = start_pos.line str.source_range = range(start_pos) return str end sass_script(:parse) end def nested_properties!(node) @expected = 'expression (e.g. 1px, bold) or "{"' block(node, :property) end def expr(allow_var = true) t = term(allow_var) return unless t res = [t, str {ss}] while (o = operator) && (t = term(allow_var)) res << o << t << str {ss} end res.flatten end def term(allow_var) e = tok(NUMBER) || interp_uri || function(allow_var) || interp_string || tok(UNICODERANGE) || interp_ident || tok(HEXCOLOR) || (allow_var && var_expr) return e if e op = tok(/[+-]/) return unless op @expected = "number or function" [op, tok(NUMBER) || function(allow_var) || (allow_var && var_expr) || expr!(:interpolation)] end def function(allow_var) name = tok(FUNCTION) return unless name if name == "expression(" || name == "calc(" str, _ = Sass::Shared.balance(@scanner, ?(, ?), 1) [name, str] else [name, str {ss}, expr(allow_var), tok!(/\)/)] end end def var_expr return unless tok(/\$/) line = @line var = Sass::Script::Tree::Variable.new(tok!(IDENT)) var.line = line var end def interpolation(warn_for_color = false) return unless tok(INTERP_START) sass_script(:parse_interpolated, warn_for_color) end def string return unless tok(STRING) Sass::Script::Value::String.value(@scanner[1] || @scanner[2]) end def interp_string _interp_string(:double) || _interp_string(:single) end def interp_uri _interp_string(:uri) end def _interp_string(type) start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][false]) return unless start res = [start] mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][true] # @scanner[2].empty? means we've started an interpolated section while @scanner[2] == '#{' @scanner.pos -= 2 # Don't consume the #{ res.last.slice!(-2..-1) res << expr!(:interpolation) << tok(mid_re) end res end def interp_ident(start = IDENT) val = tok(start) || interpolation(:warn_for_color) || tok(IDENT_HYPHEN_INTERP, true) return unless val res = [val] while (val = tok(NAME) || interpolation(:warn_for_color)) res << val end res end def interp_ident_or_var id = interp_ident return id if id var = var_expr return [var] if var end def str @strs.push "" yield @strs.last ensure @strs.pop end def str? pos = @scanner.pos line = @line offset = @offset @strs.push "" throw_error {yield} && @strs.last rescue Sass::SyntaxError @scanner.pos = pos @line = line @offset = offset nil ensure @strs.pop end def node(node, start_pos, end_pos = source_position) node.line = start_pos.line node.source_range = range(start_pos, end_pos) node end @sass_script_parser = Class.new(Sass::Script::Parser) @sass_script_parser.send(:include, ScriptParser) class << self # @private attr_accessor :sass_script_parser end def sass_script(*args) parser = self.class.sass_script_parser.new(@scanner, @line, @offset, :filename => @filename, :importer => @importer) result = parser.send(*args) unless @strs.empty? # Convert to CSS manually so that comments are ignored. src = result.to_sass @strs.each {|s| s << src} end @line = parser.line @offset = parser.offset result rescue Sass::SyntaxError => e throw(:_sass_parser_error, true) if @throw_error raise e end def merge(arr) arr && Sass::Util.merge_adjacent_strings([arr].flatten) end EXPR_NAMES = { :media_query => "media query (e.g. print, screen, print and screen)", :media_query_list => "media query (e.g. print, screen, print and screen)", :media_expr => "media expression (e.g. (min-device-width: 800px))", :at_root_query => "@at-root query (e.g. (without: media))", :at_root_directive_list => '* or identifier', :pseudo_args => "expression (e.g. fr, 2n+1)", :interp_ident => "identifier", :qualified_name => "identifier", :expr => "expression (e.g. 1px, bold)", :selector_comma_sequence => "selector", :string => "string", :import_arg => "file to import (string or url())", :moz_document_function => "matching function (e.g. url-prefix(), domain())", :supports_condition => "@supports condition (e.g. (display: flexbox))", :supports_condition_in_parens => "@supports condition (e.g. (display: flexbox))", :a_n_plus_b => "An+B expression", :keyframes_selector_component => "from, to, or a percentage", :keyframes_selector => "keyframes selector (e.g. 10%)" } TOK_NAMES = Sass::Util.to_hash(Sass::SCSS::RX.constants.map do |c| [Sass::SCSS::RX.const_get(c), c.downcase] end).merge( IDENT => "identifier", /[;{}]/ => '";"', /\b(without|with)\b/ => '"with" or "without"' ) def tok?(rx) @scanner.match?(rx) end def expr!(name) e = send(name) return e if e expected(EXPR_NAMES[name] || name.to_s) end def tok!(rx) t = tok(rx) return t if t name = TOK_NAMES[rx] unless name # Display basic regexps as plain old strings source = rx.source.gsub(/\\\//, '/') string = rx.source.gsub(/\\(.)/, '\1') name = source == Regexp.escape(string) ? string.inspect : rx.inspect end expected(name) end def expected(name) throw(:_sass_parser_error, true) if @throw_error self.class.expected(@scanner, @expected || name, @line) end def err(msg) throw(:_sass_parser_error, true) if @throw_error raise Sass::SyntaxError.new(msg, :line => @line) end def throw_error old_throw_error, @throw_error = @throw_error, false yield ensure @throw_error = old_throw_error end def catch_error(&block) old_throw_error, @throw_error = @throw_error, true pos = @scanner.pos line = @line offset = @offset expected = @expected if catch(:_sass_parser_error) {yield; false} @scanner.pos = pos @line = line @offset = offset @expected = expected {:pos => pos, :line => line, :expected => @expected, :block => block} end ensure @throw_error = old_throw_error end def rethrow(err) if @throw_error throw :_sass_parser_error, err else @scanner = Sass::Util::MultibyteStringScanner.new(@scanner.string) @scanner.pos = err[:pos] @line = err[:line] @expected = err[:expected] err[:block].call end end # @private def self.expected(scanner, expected, line) pos = scanner.pos after = scanner.string[0...pos] # Get rid of whitespace between pos and the last token, # but only if there's a newline in there after.gsub!(/\s*\n\s*$/, '') # Also get rid of stuff before the last newline after.gsub!(/.*\n/, '') after = "..." + after[-15..-1] if after.size > 18 was = scanner.rest.dup # Get rid of whitespace between pos and the next token, # but only if there's a newline in there was.gsub!(/^\s*\n\s*/, '') # Also get rid of stuff after the next newline was.gsub!(/\n.*/, '') was = was[0...15] + "..." if was.size > 18 raise Sass::SyntaxError.new( "Invalid CSS after \"#{after}\": expected #{expected}, was \"#{was}\"", :line => line) end # Avoid allocating lots of new strings for `#tok`. # This is important because `#tok` is called all the time. NEWLINE = "\n" def tok(rx, last_group_lookahead = false) res = @scanner.scan(rx) if res # This fixes https://github.com/nex3/sass/issues/104, which affects # Ruby 1.8.7 and REE. This fix is to replace the ?= zero-width # positive lookahead operator in the Regexp (which matches without # consuming the matched group), with a match that does consume the # group, but then rewinds the scanner and removes the group from the # end of the matched string. This fix makes the assumption that the # matched group will always occur at the end of the match. if last_group_lookahead && @scanner[-1] @scanner.pos -= @scanner[-1].length res.slice!(-@scanner[-1].length..-1) end newline_count = res.count(NEWLINE) if newline_count > 0 @line += newline_count @offset = res[res.rindex(NEWLINE)..-1].size else @offset += res.size end @expected = nil if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT @strs.each {|s| s << res} end res end end # Remove a vendor prefix from `str`. def deprefix(str) str.gsub(/^-[a-zA-Z0-9]+-/, '') end end end end
# File lib/sass/scss/parser.rb, line 519 def at_root_directive(start_pos) if tok?(/\(/) && (expr = at_root_query) return block(node(Sass::Tree::AtRootNode.new(expr), start_pos), :directive) end at_root_node = node(Sass::Tree::AtRootNode.new, start_pos) rule_node = ruleset return block(at_root_node, :stylesheet) unless rule_node at_root_node << rule_node at_root_node end
# File lib/sass/scss/parser.rb, line 531 def at_root_directive_list return unless (first = tok(IDENT)) arr = [first] ss while (e = tok(IDENT)) arr << e ss end arr end
# File lib/sass/scss/parser.rb, line 652 def block(node, context) node.has_children = true tok!(/\{/) block_contents(node, context) tok!(/\}/) node end
# File lib/sass/scss/parser.rb, line 671 def block_child(context) return variable || directive if context == :function return variable || directive || ruleset if context == :stylesheet variable || directive || declaration_or_ruleset end
A block may contain declarations and/or rulesets
# File lib/sass/scss/parser.rb, line 661 def block_contents(node, context) block_given? ? yield : ss_comments(node) node << (child = block_child(context)) while tok(/;/) || has_children?(child) block_given? ? yield : ss_comments(node) node << (child = block_child(context)) end node end
# File lib/sass/scss/parser.rb, line 484 def charset_directive(start_pos) name = expr!(:string) ss node(Sass::Tree::CharsetNode.new(name), start_pos) end
# File lib/sass/scss/parser.rb, line 251 def content_directive(start_pos) ss node(Sass::Tree::ContentNode.new, start_pos) end
# File lib/sass/scss/parser.rb, line 267 def debug_directive(start_pos) node(Sass::Tree::DebugNode.new(sass_script(:parse)), start_pos) end
When parsing the contents of a ruleset, it can be difficult to tell declarations apart from nested rulesets. Since we don't thoroughly parse selectors until after resolving interpolation, we can share a bunch of the parsing of the two, but we need to disambiguate them first. We use the following criteria:
-
If the entity doesn't start with an identifier followed by a colon, it's a selector. There are some additional mostly-unimportant cases here to support various declaration hacks.
-
If the colon is followed by another colon, it's a selector.
-
Otherwise, if the colon is followed by anything other than interpolation or a character that's valid as the beginning of an identifier, it's a declaration.
-
If the colon is followed by interpolation or a valid identifier, try parsing it as a declaration value. If this fails, backtrack and parse it as a selector.
-
If the declaration value value valid but is followed by “{”, backtrack and parse it as a selector anyway. This ensures that “.foo:bar {” is always parsed as a selector and never as a property with nested properties beneath it.
# File lib/sass/scss/parser.rb, line 707 def declaration_or_ruleset start_pos = source_position declaration = try_declaration if declaration.nil? return unless (selector = almost_any_value) elsif declaration.is_a?(Array) selector = declaration else # Declaration should be a PropNode. return declaration end if (additional_selector = almost_any_value) selector << additional_selector end block(node( Sass::Tree::RuleNode.new(merge(selector), range(start_pos)), start_pos), :ruleset) end
# File lib/sass/scss/parser.rb, line 191 def directive start_pos = source_position return unless tok(/@/) name = tok!(IDENT) ss if (dir = special_directive(name, start_pos)) return dir elsif (dir = prefixed_directive(name, start_pos)) return dir end val = almost_any_value val = val ? ["@#{name} "] + Sass::Util.strip_string_array(val) : ["@#{name}"] directive_body(val, start_pos) end
# File lib/sass/scss/parser.rb, line 208 def directive_body(value, start_pos) node = Sass::Tree::DirectiveNode.new(value) if tok(/\{/) node.has_children = true block_contents(node, :directive) tok!(/\}/) end node(node, start_pos) end
# File lib/sass/scss/parser.rb, line 292 def each_directive(start_pos) tok!(/\$/) vars = [tok!(IDENT)] ss while tok(/,/) ss tok!(/\$/) vars << tok!(IDENT) ss end tok!(/in/) list = sass_script(:parse) ss block(node(Sass::Tree::EachNode.new(vars, list), start_pos), :directive) end
# File lib/sass/scss/parser.rb, line 333 def else_block(node) start_pos = source_position return unless tok(/@else/) ss else_node = block( node(Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))), start_pos), :directive) node.add_else(else_node) pos = @scanner.pos line = @line ss else_block(node) || begin # Backtrack in case there are any comments we want to parse @scanner.pos = pos @line = line node end end
# File lib/sass/scss/parser.rb, line 354 def else_directive(start_pos) err("Invalid CSS: @else must come after @if") end
# File lib/sass/scss/parser.rb, line 542 def error_directive(start_pos) node(Sass::Tree::ErrorNode.new(sass_script(:parse)), start_pos) end
# File lib/sass/scss/parser.rb, line 358 def extend_directive(start_pos) selector_start_pos = source_position @expected = "selector" selector = Sass::Util.strip_string_array(expr!(:almost_any_value)) optional = tok(OPTIONAL) ss node(Sass::Tree::ExtendNode.new(selector, !!optional, range(selector_start_pos)), start_pos) end
# File lib/sass/scss/parser.rb, line 275 def for_directive(start_pos) tok!(/\$/) var = tok! IDENT ss tok!(/from/) from = sass_script(:parse_until, Set["to", "through"]) ss @expected = '"to" or "through"' exclusive = (tok(/to/) || tok!(/through/)) == 'to' to = sass_script(:parse) ss block(node(Sass::Tree::ForNode.new(var, from, to, exclusive), start_pos), :directive) end
# File lib/sass/scss/parser.rb, line 256 def function_directive(start_pos) name = tok! IDENT args, splat = sass_script(:parse_function_definition_arglist) ss block(node(Sass::Tree::FunctionNode.new(name, args, splat), start_pos), :function) end
# File lib/sass/scss/parser.rb, line 677 def has_children?(child_or_array) return false unless child_or_array return child_or_array.last.has_children if child_or_array.is_a?(Array) child_or_array.has_children end
# File lib/sass/scss/parser.rb, line 316 def if_directive(start_pos) expr = sass_script(:parse) ss node = block(node(Sass::Tree::IfNode.new(expr), start_pos), :directive) pos = @scanner.pos line = @line ss else_block(node) || begin # Backtrack in case there are any comments we want to parse @scanner.pos = pos @line = line node end end
# File lib/sass/scss/parser.rb, line 380 def import_arg start_pos = source_position return unless (str = string) || (uri = tok?(/url\(/i)) if uri str = sass_script(:parse_string) ss supports = supports_clause ss media = media_query_list ss return node(Tree::CssImportNode.new(str, media.to_a, supports), start_pos) end ss supports = supports_clause ss media = media_query_list if str =~ %r{^(https?:)?//} || media || supports || use_css_import? return node(Sass::Tree::CssImportNode.new( Sass::Script::Value::String.quote(str), media.to_a, supports), start_pos) end node(Sass::Tree::ImportNode.new(str.strip), start_pos) end
# File lib/sass/scss/parser.rb, line 367 def import_directive(start_pos) values = [] loop do values << expr!(:import_arg) break if use_css_import? break unless tok(/,/) ss end values end
# File lib/sass/scss/parser.rb, line 237 def include_directive(start_pos) name = tok! IDENT args, keywords, splat, kwarg_splat = sass_script(:parse_mixin_include_arglist) ss include_node = node( Sass::Tree::MixinNode.new(name, args, keywords, splat, kwarg_splat), start_pos) if tok?(/\{/) include_node.has_children = true block(include_node, :directive) else include_node end end
# File lib/sass/scss/parser.rb, line 114 def init_scanner! @scanner = if @template.is_a?(StringScanner) @template else Sass::Util::MultibyteStringScanner.new(@template.gsub("\r", "")) end end
# File lib/sass/scss/parser.rb, line 407 def media_directive(start_pos) block(node(Sass::Tree::MediaNode.new(expr!(:media_query_list).to_a), start_pos), :directive) end
Aliases allow us to use different descriptions if the same expression fails in different contexts.
# File lib/sass/scss/parser.rb, line 426 def media_query if (ident1 = interp_ident) ss ident2 = interp_ident ss if ident2 && ident2.length == 1 && ident2[0].is_a?(String) && ident2[0].downcase == 'and' query = Sass::Media::Query.new([], ident1, []) else if ident2 query = Sass::Media::Query.new(ident1, ident2, []) else query = Sass::Media::Query.new([], ident1, []) end return query unless tok(/and/i) ss end end if query expr = expr!(:media_expr) else expr = media_expr return unless expr end query ||= Sass::Media::Query.new([], [], []) query.expressions << expr ss while tok(/and/i) ss; query.expressions << expr!(:media_expr) end query end
www.w3.org/TR/css3-mediaqueries/#syntax
# File lib/sass/scss/parser.rb, line 412 def media_query_list query = media_query return unless query queries = [query] ss while tok(/,/) ss; queries << expr!(:media_query) end ss Sass::Media::QueryList.new(queries) end
# File lib/sass/scss/parser.rb, line 230 def mixin_directive(start_pos) name = tok! IDENT args, splat = sass_script(:parse_mixin_definition_arglist) ss block(node(Sass::Tree::MixinDefNode.new(name, args, splat), start_pos), :directive) end
# File lib/sass/scss/parser.rb, line 511 def moz_document_function val = interp_uri || _interp_string(:url_prefix) || _interp_string(:domain) || function(!:allow_var) || interpolation return unless val ss val end
# File lib/sass/scss/parser.rb, line 637 def operator # Many of these operators (all except / and ,) # are disallowed by the CSS spec, # but they're included here for compatibility # with some proprietary MS properties str {ss if tok(/[\/,:.=]/)} end
# File lib/sass/scss/parser.rb, line 225 def prefixed_directive(name, start_pos) sym = deprefix(name).tr('-', '_').to_sym PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name, start_pos) end
# File lib/sass/scss/parser.rb, line 157 def process_comment(text, node) silent = text =~ %r{\A//} loud = !silent && text =~ %r{\A/[/*]!} line = @line - text.count("\n") comment_start = @scanner.pos - text.length index_before_line = @scanner.string.rindex("\n", comment_start) || -1 offset = comment_start - index_before_line if silent value = [text.sub(%r{\A\s*//}, '/*').gsub(%r{^\s*//}, ' *') + ' */'] else value = Sass::Engine.parse_interp(text, line, offset, :filename => @filename) line_before_comment = @scanner.string[index_before_line + 1...comment_start] value.unshift(line_before_comment.gsub(/[^\s]/, ' ')) end type = if silent :silent elsif loud :loud else :normal end start_pos = Sass::Source::Position.new(line, offset) comment = node(Sass::Tree::CommentNode.new(value, type), start_pos) node << comment end
# File lib/sass/scss/parser.rb, line 461 def query_expr interp = interpolation return interp if interp return unless tok(/\(/) res = ['('] ss res << sass_script(:parse) if tok(/:/) res << ': ' ss res << sass_script(:parse) end res << tok!(/\)/) ss res end
# File lib/sass/scss/parser.rb, line 110 def range(start_pos, end_pos = source_position) Sass::Source::Range.new(start_pos, end_pos, @filename, @importer) end
# File lib/sass/scss/parser.rb, line 263 def return_directive(start_pos) node(Sass::Tree::ReturnNode.new(sass_script(:parse)), start_pos) end
# File lib/sass/scss/parser.rb, line 645 def ruleset start_pos = source_position return unless (rules = almost_any_value) block(node( Sass::Tree::RuleNode.new(rules, range(start_pos)), start_pos), :ruleset) end
# File lib/sass/scss/parser.rb, line 128 def s(node) while tok(S) || tok(CDC) || tok(CDO) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT)) next unless c process_comment c, node c = nil end true end
# File lib/sass/scss/parser.rb, line 106 def source_position Sass::Source::Position.new(@line, @offset) end
# File lib/sass/scss/parser.rb, line 220 def special_directive(name, start_pos) sym = name.tr('-', '_').to_sym DIRECTIVES.include?(sym) && send("#{sym}_directive", start_pos) end
# File lib/sass/scss/parser.rb, line 137 def ss nil while tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT) true end
# File lib/sass/scss/parser.rb, line 142 def ss_comments(node) while tok(S) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT)) next unless c process_comment c, node c = nil end true end
# File lib/sass/scss/parser.rb, line 123 def stylesheet node = node(Sass::Tree::RootNode.new(@scanner.string), source_position) block_contents(node, :stylesheet) {s(node)} end
# File lib/sass/scss/parser.rb, line 559 def supports_clause return unless tok(/supports\(/i) ss supports = supports_condition ss tok!(/\)/) supports end
# File lib/sass/scss/parser.rb, line 568 def supports_condition supports_negation || supports_operator || supports_interpolation end
# File lib/sass/scss/parser.rb, line 591 def supports_condition_in_parens interp = supports_interpolation return interp if interp return unless tok(/\(/); ss if (cond = supports_condition) tok!(/\)/); ss cond else name = sass_script(:parse) tok!(/:/); ss value = sass_script(:parse) tok!(/\)/); ss Sass::Supports::Declaration.new(name, value) end end
www.w3.org/TR/css3-conditional/
# File lib/sass/scss/parser.rb, line 547 def supports_directive(name, start_pos) condition = expr!(:supports_condition) node = Sass::Tree::SupportsNode.new(name, condition) tok!(/\{/) node.has_children = true block_contents(node, :directive) tok!(/\}/) node(node, start_pos) end
# File lib/sass/scss/parser.rb, line 607 def supports_interpolation interp = interpolation return unless interp ss Sass::Supports::Interpolation.new(interp) end
# File lib/sass/scss/parser.rb, line 572 def supports_negation return unless tok(/not/i) ss Sass::Supports::Negation.new(expr!(:supports_condition_in_parens)) end
# File lib/sass/scss/parser.rb, line 578 def supports_operator cond = supports_condition_in_parens return unless cond re = /and|or/i while (op = tok(re)) re = /#{op}/i ss cond = Sass::Supports::Operator.new( cond, expr!(:supports_condition_in_parens), op) end cond end
Tries to parse a declaration, and returns the value parsed so far if it fails.
This has three possible return types. It can return `nil`, indicating that parsing failed completely and the scanner hasn't moved forward at all. It can return an Array, indicating that parsing failed after consuming some text (possibly containing interpolation), which is returned. Or it can return a PropNode, indicating that parsing succeeded.
# File lib/sass/scss/parser.rb, line 737 def try_declaration # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop: # val" hacks. name_start_pos = source_position if (s = tok(/[:\*\.]|\#(?!\{)/)) name = [s, str {ss}] return name unless (ident = interp_ident) name << ident else return unless (name = interp_ident) name = Array(name) end if (comment = tok(COMMENT)) name << comment end name_end_pos = source_position mid = [str {ss}] return name + mid unless tok(/:/) mid << ':' return name + mid + [':'] if tok(/:/) mid << str {ss} post_colon_whitespace = !mid.last.empty? could_be_selector = !post_colon_whitespace && (tok?(IDENT_START) || tok?(INTERP_START)) value_start_pos = source_position value = nil error = catch_error do value = value! if tok?(/\{/) # Properties that are ambiguous with selectors can't have additional # properties nested beneath them. tok!(/;/) if could_be_selector elsif !tok?(/[;{}]/) # We want an exception if there's no valid end-of-property character # exists, but we don't want to consume it if it does. tok!(/[;{}]/) end end if error rethrow error unless could_be_selector # If the value would be followed by a semicolon, it's definitely # supposed to be a property, not a selector. additional_selector = almost_any_value rethrow error if tok?(/;/) return name + mid + (additional_selector || []) end value_end_pos = source_position ss require_block = tok?(/\{/) node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new), name_start_pos, value_end_pos) node.name_source_range = range(name_start_pos, name_end_pos) node.value_source_range = range(value_start_pos, value_end_pos) return node unless require_block nested_properties! node end
# File lib/sass/scss/parser.rb, line 405 def use_css_import?; false; end
# File lib/sass/scss/parser.rb, line 614 def variable return unless tok(/\$/) start_pos = source_position name = tok!(IDENT) ss; tok!(/:/); ss expr = sass_script(:parse) while tok(/!/) flag_name = tok!(IDENT) if flag_name == 'default' guarded ||= true elsif flag_name == 'global' global ||= true else raise Sass::SyntaxError.new("Invalid flag \"!#{flag_name}\".", :line => @line) end ss end result = Sass::Tree::VariableNode.new(name, expr, guarded, global) node(result, start_pos) end
# File lib/sass/scss/parser.rb, line 271 def warn_directive(start_pos) node(Sass::Tree::WarnNode.new(sass_script(:parse)), start_pos) end
# File lib/sass/scss/parser.rb, line 310 def while_directive(start_pos) expr = sass_script(:parse) ss block(node(Sass::Tree::WhileNode.new(expr), start_pos), :directive) end
# File lib/sass/scss/parser.rb, line 152 def whitespace return unless tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT) ss end