Revert "refactor(core): ensure CSS parser uses ParseSourceSpan to track ast locations"
This reverts commit 5c9f871b21c6ad9fce5ba05f013bc45fb0bde851.
This commit is contained in:
		
							parent
							
								
									5c9f871b21
								
							
						
					
					
						commit
						74b45dfbf8
					
				@ -1,223 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @license
 | 
			
		||||
 * Copyright Google Inc. All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Use of this source code is governed by an MIT-style license that can be
 | 
			
		||||
 * found in the LICENSE file at https://angular.io/license
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import {CssToken, CssTokenType} from './css_lexer';
 | 
			
		||||
import {BlockType, mergeTokens} from './css_parser';
 | 
			
		||||
import {ParseLocation, ParseSourceSpan} from './parse_util';
 | 
			
		||||
 | 
			
		||||
export interface CssAstVisitor {
 | 
			
		||||
  visitCssValue(ast: CssStyleValueAst, context?: any): any;
 | 
			
		||||
  visitCssInlineRule(ast: CssInlineRuleAst, context?: any): any;
 | 
			
		||||
  visitCssAtRulePredicate(ast: CssAtRulePredicateAst, context?: any): any;
 | 
			
		||||
  visitCssKeyframeRule(ast: CssKeyframeRuleAst, context?: any): any;
 | 
			
		||||
  visitCssKeyframeDefinition(ast: CssKeyframeDefinitionAst, context?: any): any;
 | 
			
		||||
  visitCssMediaQueryRule(ast: CssMediaQueryRuleAst, context?: any): any;
 | 
			
		||||
  visitCssSelectorRule(ast: CssSelectorRuleAst, context?: any): any;
 | 
			
		||||
  visitCssSelector(ast: CssSelectorAst, context?: any): any;
 | 
			
		||||
  visitCssSimpleSelector(ast: CssSimpleSelectorAst, context?: any): any;
 | 
			
		||||
  visitCssPseudoSelector(ast: CssPseudoSelectorAst, context?: any): any;
 | 
			
		||||
  visitCssDefinition(ast: CssDefinitionAst, context?: any): any;
 | 
			
		||||
  visitCssBlock(ast: CssBlockAst, context?: any): any;
 | 
			
		||||
  visitCssStylesBlock(ast: CssStylesBlockAst, context?: any): any;
 | 
			
		||||
  visitCssStyleSheet(ast: CssStyleSheetAst, context?: any): any;
 | 
			
		||||
  visitCssUnknownRule(ast: CssUnknownRuleAst, context?: any): any;
 | 
			
		||||
  visitCssUnknownTokenList(ast: CssUnknownTokenListAst, context?: any): any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export abstract class CssAst {
 | 
			
		||||
  constructor(public location: ParseSourceSpan) {}
 | 
			
		||||
  get start(): ParseLocation { return this.location.start; }
 | 
			
		||||
  get end(): ParseLocation { return this.location.end; }
 | 
			
		||||
  abstract visit(visitor: CssAstVisitor, context?: any): any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssStyleValueAst extends CssAst {
 | 
			
		||||
  constructor(location: ParseSourceSpan, public tokens: CssToken[], public strValue: string) {
 | 
			
		||||
    super(location);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any { return visitor.visitCssValue(this); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export abstract class CssRuleAst extends CssAst {
 | 
			
		||||
  constructor(location: ParseSourceSpan) { super(location); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssBlockRuleAst extends CssRuleAst {
 | 
			
		||||
  constructor(
 | 
			
		||||
      public location: ParseSourceSpan, public type: BlockType, public block: CssBlockAst,
 | 
			
		||||
      public name: CssToken = null) {
 | 
			
		||||
    super(location);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssBlock(this.block, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssKeyframeRuleAst extends CssBlockRuleAst {
 | 
			
		||||
  constructor(location: ParseSourceSpan, name: CssToken, block: CssBlockAst) {
 | 
			
		||||
    super(location, BlockType.Keyframes, block, name);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssKeyframeRule(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssKeyframeDefinitionAst extends CssBlockRuleAst {
 | 
			
		||||
  constructor(location: ParseSourceSpan, public steps: CssToken[], block: CssBlockAst) {
 | 
			
		||||
    super(location, BlockType.Keyframes, block, mergeTokens(steps, ','));
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssKeyframeDefinition(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssBlockDefinitionRuleAst extends CssBlockRuleAst {
 | 
			
		||||
  constructor(
 | 
			
		||||
      location: ParseSourceSpan, public strValue: string, type: BlockType,
 | 
			
		||||
      public query: CssAtRulePredicateAst, block: CssBlockAst) {
 | 
			
		||||
    super(location, type, block);
 | 
			
		||||
    var firstCssToken: CssToken = query.tokens[0];
 | 
			
		||||
    this.name = new CssToken(
 | 
			
		||||
        firstCssToken.index, firstCssToken.column, firstCssToken.line, CssTokenType.Identifier,
 | 
			
		||||
        this.strValue);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssBlock(this.block, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssMediaQueryRuleAst extends CssBlockDefinitionRuleAst {
 | 
			
		||||
  constructor(
 | 
			
		||||
      location: ParseSourceSpan, strValue: string, query: CssAtRulePredicateAst,
 | 
			
		||||
      block: CssBlockAst) {
 | 
			
		||||
    super(location, strValue, BlockType.MediaQuery, query, block);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssMediaQueryRule(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssAtRulePredicateAst extends CssAst {
 | 
			
		||||
  constructor(location: ParseSourceSpan, public strValue: string, public tokens: CssToken[]) {
 | 
			
		||||
    super(location);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssAtRulePredicate(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssInlineRuleAst extends CssRuleAst {
 | 
			
		||||
  constructor(location: ParseSourceSpan, public type: BlockType, public value: CssStyleValueAst) {
 | 
			
		||||
    super(location);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssInlineRule(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssSelectorRuleAst extends CssBlockRuleAst {
 | 
			
		||||
  public strValue: string;
 | 
			
		||||
 | 
			
		||||
  constructor(location: ParseSourceSpan, public selectors: CssSelectorAst[], block: CssBlockAst) {
 | 
			
		||||
    super(location, BlockType.Selector, block);
 | 
			
		||||
    this.strValue = selectors.map(selector => selector.strValue).join(',');
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssSelectorRule(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssDefinitionAst extends CssAst {
 | 
			
		||||
  constructor(
 | 
			
		||||
      location: ParseSourceSpan, public property: CssToken, public value: CssStyleValueAst) {
 | 
			
		||||
    super(location);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssDefinition(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export abstract class CssSelectorPartAst extends CssAst {
 | 
			
		||||
  constructor(location: ParseSourceSpan) { super(location); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssSelectorAst extends CssSelectorPartAst {
 | 
			
		||||
  public strValue: string;
 | 
			
		||||
  constructor(location: ParseSourceSpan, public selectorParts: CssSimpleSelectorAst[]) {
 | 
			
		||||
    super(location);
 | 
			
		||||
    this.strValue = selectorParts.map(part => part.strValue).join('');
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssSelector(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssSimpleSelectorAst extends CssSelectorPartAst {
 | 
			
		||||
  constructor(
 | 
			
		||||
      location: ParseSourceSpan, public tokens: CssToken[], public strValue: string,
 | 
			
		||||
      public pseudoSelectors: CssPseudoSelectorAst[], public operator: CssToken) {
 | 
			
		||||
    super(location);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssSimpleSelector(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssPseudoSelectorAst extends CssSelectorPartAst {
 | 
			
		||||
  constructor(
 | 
			
		||||
      location: ParseSourceSpan, public strValue: string, public name: string,
 | 
			
		||||
      public tokens: CssToken[], public innerSelectors: CssSelectorAst[]) {
 | 
			
		||||
    super(location);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssPseudoSelector(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssBlockAst extends CssAst {
 | 
			
		||||
  constructor(location: ParseSourceSpan, public entries: CssAst[]) { super(location); }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any { return visitor.visitCssBlock(this, context); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 a style block is different from a standard block because it contains
 | 
			
		||||
 css prop:value definitions. A regular block can contain a list of Ast entries.
 | 
			
		||||
 */
 | 
			
		||||
export class CssStylesBlockAst extends CssBlockAst {
 | 
			
		||||
  constructor(location: ParseSourceSpan, public definitions: CssDefinitionAst[]) {
 | 
			
		||||
    super(location, definitions);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssStylesBlock(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssStyleSheetAst extends CssAst {
 | 
			
		||||
  constructor(location: ParseSourceSpan, public rules: CssAst[]) { super(location); }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssStyleSheet(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssUnknownRuleAst extends CssRuleAst {
 | 
			
		||||
  constructor(location: ParseSourceSpan, public ruleName: string, public tokens: CssToken[]) {
 | 
			
		||||
    super(location);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssUnknownRule(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssUnknownTokenListAst extends CssRuleAst {
 | 
			
		||||
  constructor(location: ParseSourceSpan, public name: string, public tokens: CssToken[]) {
 | 
			
		||||
    super(location);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssUnknownTokenList(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -205,10 +205,10 @@ export class CssScanner {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!isPresent(next)) {
 | 
			
		||||
      next = new CssToken(this.index, this.column, this.line, CssTokenType.EOF, 'end of file');
 | 
			
		||||
      next = new CssToken(0, 0, 0, CssTokenType.EOF, 'end of file');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var isMatchingType: boolean = false;
 | 
			
		||||
    var isMatchingType: boolean;
 | 
			
		||||
    if (type == CssTokenType.IdentifierOrNumber) {
 | 
			
		||||
      // TODO (matsko): implement array traversal for lookup here
 | 
			
		||||
      isMatchingType = next.type == CssTokenType.Number || next.type == CssTokenType.Identifier;
 | 
			
		||||
 | 
			
		||||
@ -7,8 +7,7 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import * as chars from './chars';
 | 
			
		||||
import {CssAst, CssAtRulePredicateAst, CssBlockAst, CssBlockDefinitionRuleAst, CssBlockRuleAst, CssDefinitionAst, CssInlineRuleAst, CssKeyframeDefinitionAst, CssKeyframeRuleAst, CssMediaQueryRuleAst, CssPseudoSelectorAst, CssRuleAst, CssSelectorAst, CssSelectorRuleAst, CssSimpleSelectorAst, CssStyleSheetAst, CssStyleValueAst, CssStylesBlockAst, CssUnknownRuleAst, CssUnknownTokenListAst} from './css_ast';
 | 
			
		||||
import {CssLexer, CssLexerMode, CssScanner, CssToken, CssTokenType, generateErrorMessage, isNewline} from './css_lexer';
 | 
			
		||||
import {CssLexerMode, CssScanner, CssToken, CssTokenType, generateErrorMessage, isNewline} from './css_lexer';
 | 
			
		||||
import {isPresent} from './facade/lang';
 | 
			
		||||
import {ParseError, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
 | 
			
		||||
 | 
			
		||||
@ -63,7 +62,7 @@ function isSelectorOperatorCharacter(code: number): boolean {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function mergeTokens(tokens: CssToken[], separator: string = ''): CssToken {
 | 
			
		||||
function mergeTokens(tokens: CssToken[], separator: string = ''): CssToken {
 | 
			
		||||
  var mainToken = tokens[0];
 | 
			
		||||
  var str = mainToken.strValue;
 | 
			
		||||
  for (var i = 1; i < tokens.length; i++) {
 | 
			
		||||
@ -105,6 +104,30 @@ function characterContainsDelimiter(code: number, delimiters: number): boolean {
 | 
			
		||||
  return (getDelimFromCharacter(code) & delimiters) > 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export abstract class CssAst {
 | 
			
		||||
  constructor(public start: number, public end: number) {}
 | 
			
		||||
  abstract visit(visitor: CssAstVisitor, context?: any): any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface CssAstVisitor {
 | 
			
		||||
  visitCssValue(ast: CssStyleValueAst, context?: any): any;
 | 
			
		||||
  visitCssInlineRule(ast: CssInlineRuleAst, context?: any): any;
 | 
			
		||||
  visitCssAtRulePredicate(ast: CssAtRulePredicateAst, context?: any): any;
 | 
			
		||||
  visitCssKeyframeRule(ast: CssKeyframeRuleAst, context?: any): any;
 | 
			
		||||
  visitCssKeyframeDefinition(ast: CssKeyframeDefinitionAst, context?: any): any;
 | 
			
		||||
  visitCssMediaQueryRule(ast: CssMediaQueryRuleAst, context?: any): any;
 | 
			
		||||
  visitCssSelectorRule(ast: CssSelectorRuleAst, context?: any): any;
 | 
			
		||||
  visitCssSelector(ast: CssSelectorAst, context?: any): any;
 | 
			
		||||
  visitCssSimpleSelector(ast: CssSimpleSelectorAst, context?: any): any;
 | 
			
		||||
  visitCssPseudoSelector(ast: CssPseudoSelectorAst, context?: any): any;
 | 
			
		||||
  visitCssDefinition(ast: CssDefinitionAst, context?: any): any;
 | 
			
		||||
  visitCssBlock(ast: CssBlockAst, context?: any): any;
 | 
			
		||||
  visitCssStylesBlock(ast: CssStylesBlockAst, context?: any): any;
 | 
			
		||||
  visitCssStyleSheet(ast: CssStyleSheetAst, context?: any): any;
 | 
			
		||||
  visitCssUnknownRule(ast: CssUnknownRuleAst, context?: any): any;
 | 
			
		||||
  visitCssUnknownTokenList(ast: CssUnknownTokenListAst, context?: any): any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ParsedCssResult {
 | 
			
		||||
  constructor(public errors: CssParseError[], public ast: CssStyleSheetAst) {}
 | 
			
		||||
}
 | 
			
		||||
@ -112,89 +135,9 @@ export class ParsedCssResult {
 | 
			
		||||
export class CssParser {
 | 
			
		||||
  private _errors: CssParseError[] = [];
 | 
			
		||||
  private _file: ParseSourceFile;
 | 
			
		||||
  private _scanner: CssScanner;
 | 
			
		||||
  private _lastToken: CssToken;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param css the CSS code that will be parsed
 | 
			
		||||
   * @param url the name of the CSS file containing the CSS source code
 | 
			
		||||
   */
 | 
			
		||||
  parse(css: string, url: string): ParsedCssResult {
 | 
			
		||||
    var lexer = new CssLexer();
 | 
			
		||||
    this._file = new ParseSourceFile(css, url);
 | 
			
		||||
    this._scanner = lexer.scan(css, false);
 | 
			
		||||
 | 
			
		||||
    var ast = this._parseStyleSheet(EOF_DELIM_FLAG);
 | 
			
		||||
 | 
			
		||||
    var errors = this._errors;
 | 
			
		||||
    this._errors = [];
 | 
			
		||||
 | 
			
		||||
    var result = new ParsedCssResult(errors, ast);
 | 
			
		||||
    this._file = null;
 | 
			
		||||
    this._scanner = null;
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
  _parseStyleSheet(delimiters: number): CssStyleSheetAst {
 | 
			
		||||
    var results: CssRuleAst[] = [];
 | 
			
		||||
    this._scanner.consumeEmptyStatements();
 | 
			
		||||
    while (this._scanner.peek != chars.$EOF) {
 | 
			
		||||
      this._scanner.setMode(CssLexerMode.BLOCK);
 | 
			
		||||
      results.push(this._parseRule(delimiters));
 | 
			
		||||
    }
 | 
			
		||||
    var span: ParseSourceSpan = null;
 | 
			
		||||
    if (results.length > 0) {
 | 
			
		||||
      var firstRule = results[0];
 | 
			
		||||
      // we collect the last token like so incase there was an
 | 
			
		||||
      // EOF token that was emitted sometime during the lexing
 | 
			
		||||
      span = this._generateSourceSpan(firstRule, this._lastToken);
 | 
			
		||||
    }
 | 
			
		||||
    return new CssStyleSheetAst(span, results);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
  _getSourceContent(): string { return isPresent(this._scanner) ? this._scanner.input : ''; }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
  _extractSourceContent(start: number, end: number): string {
 | 
			
		||||
    return this._getSourceContent().substring(start, end + 1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
  _generateSourceSpan(start: CssToken|CssAst, end: CssToken|CssAst = null): ParseSourceSpan {
 | 
			
		||||
    var startLoc: ParseLocation;
 | 
			
		||||
    if (start instanceof CssAst) {
 | 
			
		||||
      startLoc = start.location.start;
 | 
			
		||||
    } else {
 | 
			
		||||
      var token = start;
 | 
			
		||||
      if (!isPresent(token)) {
 | 
			
		||||
        // the data here is invalid, however, if and when this does
 | 
			
		||||
        // occur, any other errors associated with this will be collected
 | 
			
		||||
        token = this._lastToken;
 | 
			
		||||
      }
 | 
			
		||||
      startLoc = new ParseLocation(this._file, token.index, token.line, token.column);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!isPresent(end)) {
 | 
			
		||||
      end = this._lastToken;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var endLine: number;
 | 
			
		||||
    var endColumn: number;
 | 
			
		||||
    var endIndex: number;
 | 
			
		||||
    if (end instanceof CssAst) {
 | 
			
		||||
      endLine = end.location.end.line;
 | 
			
		||||
      endColumn = end.location.end.col;
 | 
			
		||||
      endIndex = end.location.end.offset;
 | 
			
		||||
    } else if (end instanceof CssToken) {
 | 
			
		||||
      endLine = end.line;
 | 
			
		||||
      endColumn = end.column;
 | 
			
		||||
      endIndex = end.index;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var endLoc = new ParseLocation(this._file, endIndex, endLine, endColumn);
 | 
			
		||||
    return new ParseSourceSpan(startLoc, endLoc);
 | 
			
		||||
  constructor(private _scanner: CssScanner, private _fileName: string) {
 | 
			
		||||
    this._file = new ParseSourceFile(this._scanner.input, _fileName);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
@ -238,6 +181,29 @@ export class CssParser {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  parse(): ParsedCssResult {
 | 
			
		||||
    var delimiters: number = EOF_DELIM_FLAG;
 | 
			
		||||
    var ast = this._parseStyleSheet(delimiters);
 | 
			
		||||
 | 
			
		||||
    var errors = this._errors;
 | 
			
		||||
    this._errors = [];
 | 
			
		||||
 | 
			
		||||
    return new ParsedCssResult(errors, ast);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
  _parseStyleSheet(delimiters: number): CssStyleSheetAst {
 | 
			
		||||
    const start = this._getScannerIndex();
 | 
			
		||||
    var results: CssAst[] = [];
 | 
			
		||||
    this._scanner.consumeEmptyStatements();
 | 
			
		||||
    while (this._scanner.peek != chars.$EOF) {
 | 
			
		||||
      this._scanner.setMode(CssLexerMode.BLOCK);
 | 
			
		||||
      results.push(this._parseRule(delimiters));
 | 
			
		||||
    }
 | 
			
		||||
    const end = this._getScannerIndex() - 1;
 | 
			
		||||
    return new CssStyleSheetAst(start, end, results);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
  _parseRule(delimiters: number): CssRuleAst {
 | 
			
		||||
    if (this._scanner.peek == chars.$AT) {
 | 
			
		||||
@ -249,10 +215,10 @@ export class CssParser {
 | 
			
		||||
  /** @internal */
 | 
			
		||||
  _parseAtRule(delimiters: number): CssRuleAst {
 | 
			
		||||
    const start = this._getScannerIndex();
 | 
			
		||||
    var end: number;
 | 
			
		||||
 | 
			
		||||
    this._scanner.setMode(CssLexerMode.BLOCK);
 | 
			
		||||
    var token = this._scan();
 | 
			
		||||
    var startToken = token;
 | 
			
		||||
 | 
			
		||||
    this._assertCondition(
 | 
			
		||||
        token.type == CssTokenType.AtKeyword,
 | 
			
		||||
@ -266,55 +232,46 @@ export class CssParser {
 | 
			
		||||
      case BlockType.Import:
 | 
			
		||||
        var value = this._parseValue(delimiters);
 | 
			
		||||
        this._scanner.setMode(CssLexerMode.BLOCK);
 | 
			
		||||
        end = value.end;
 | 
			
		||||
        this._scanner.consumeEmptyStatements();
 | 
			
		||||
        var span = this._generateSourceSpan(startToken, value);
 | 
			
		||||
        return new CssInlineRuleAst(span, type, value);
 | 
			
		||||
        return new CssInlineRuleAst(start, end, type, value);
 | 
			
		||||
 | 
			
		||||
      case BlockType.Viewport:
 | 
			
		||||
      case BlockType.FontFace:
 | 
			
		||||
        block = this._parseStyleBlock(delimiters);
 | 
			
		||||
        var span = this._generateSourceSpan(startToken, block);
 | 
			
		||||
        return new CssBlockRuleAst(span, type, block);
 | 
			
		||||
        end = this._getScannerIndex() - 1;
 | 
			
		||||
        return new CssBlockRuleAst(start, end, type, block);
 | 
			
		||||
 | 
			
		||||
      case BlockType.Keyframes:
 | 
			
		||||
        var tokens = this._collectUntilDelim(delimiters | RBRACE_DELIM_FLAG | LBRACE_DELIM_FLAG);
 | 
			
		||||
        // keyframes only have one identifier name
 | 
			
		||||
        var name = tokens[0];
 | 
			
		||||
        var block = this._parseKeyframeBlock(delimiters);
 | 
			
		||||
        var span = this._generateSourceSpan(startToken, block);
 | 
			
		||||
        return new CssKeyframeRuleAst(span, name, block);
 | 
			
		||||
        end = this._getScannerIndex() - 1;
 | 
			
		||||
        return new CssKeyframeRuleAst(start, end, name, this._parseKeyframeBlock(delimiters));
 | 
			
		||||
 | 
			
		||||
      case BlockType.MediaQuery:
 | 
			
		||||
        this._scanner.setMode(CssLexerMode.MEDIA_QUERY);
 | 
			
		||||
        var tokens = this._collectUntilDelim(delimiters | RBRACE_DELIM_FLAG | LBRACE_DELIM_FLAG);
 | 
			
		||||
        var endToken = tokens[tokens.length - 1];
 | 
			
		||||
        // we do not track the whitespace after the mediaQuery predicate ends
 | 
			
		||||
        // so we have to calculate the end string value on our own
 | 
			
		||||
        var end = endToken.index + endToken.strValue.length - 1;
 | 
			
		||||
        var strValue = this._extractSourceContent(start, end);
 | 
			
		||||
        var span = this._generateSourceSpan(startToken, endToken);
 | 
			
		||||
        var query = new CssAtRulePredicateAst(span, strValue, tokens);
 | 
			
		||||
        end = this._getScannerIndex() - 1;
 | 
			
		||||
        var strValue = this._scanner.input.substring(start, end);
 | 
			
		||||
        var query = new CssAtRulePredicateAst(start, end, strValue, tokens);
 | 
			
		||||
        block = this._parseBlock(delimiters);
 | 
			
		||||
        strValue = this._extractSourceContent(start, this._getScannerIndex() - 1);
 | 
			
		||||
        span = this._generateSourceSpan(startToken, block);
 | 
			
		||||
        return new CssMediaQueryRuleAst(span, strValue, query, block);
 | 
			
		||||
        end = this._getScannerIndex() - 1;
 | 
			
		||||
        strValue = this._scanner.input.substring(start, end);
 | 
			
		||||
        return new CssMediaQueryRuleAst(start, end, strValue, query, block);
 | 
			
		||||
 | 
			
		||||
      case BlockType.Document:
 | 
			
		||||
      case BlockType.Supports:
 | 
			
		||||
      case BlockType.Page:
 | 
			
		||||
        this._scanner.setMode(CssLexerMode.AT_RULE_QUERY);
 | 
			
		||||
        var tokens = this._collectUntilDelim(delimiters | RBRACE_DELIM_FLAG | LBRACE_DELIM_FLAG);
 | 
			
		||||
        var endToken = tokens[tokens.length - 1];
 | 
			
		||||
        // we do not track the whitespace after this block rule predicate ends
 | 
			
		||||
        // so we have to calculate the end string value on our own
 | 
			
		||||
        var end = endToken.index + endToken.strValue.length - 1;
 | 
			
		||||
        var strValue = this._extractSourceContent(start, end);
 | 
			
		||||
        var span = this._generateSourceSpan(startToken, tokens[tokens.length - 1]);
 | 
			
		||||
        var query = new CssAtRulePredicateAst(span, strValue, tokens);
 | 
			
		||||
        end = this._getScannerIndex() - 1;
 | 
			
		||||
        var strValue = this._scanner.input.substring(start, end);
 | 
			
		||||
        var query = new CssAtRulePredicateAst(start, end, strValue, tokens);
 | 
			
		||||
        block = this._parseBlock(delimiters);
 | 
			
		||||
        strValue = this._extractSourceContent(start, block.end.offset);
 | 
			
		||||
        span = this._generateSourceSpan(startToken, block);
 | 
			
		||||
        return new CssBlockDefinitionRuleAst(span, strValue, type, query, block);
 | 
			
		||||
        end = this._getScannerIndex() - 1;
 | 
			
		||||
        strValue = this._scanner.input.substring(start, end);
 | 
			
		||||
        return new CssBlockDefinitionRuleAst(start, end, strValue, type, query, block);
 | 
			
		||||
 | 
			
		||||
      // if a custom @rule { ... } is used it should still tokenize the insides
 | 
			
		||||
      default:
 | 
			
		||||
@ -323,9 +280,8 @@ export class CssParser {
 | 
			
		||||
        this._scanner.setMode(CssLexerMode.ALL);
 | 
			
		||||
        this._error(
 | 
			
		||||
            generateErrorMessage(
 | 
			
		||||
                this._getSourceContent(),
 | 
			
		||||
                `The CSS "at" rule "${tokenName}" is not allowed to used here`, token.strValue,
 | 
			
		||||
                token.index, token.line, token.column),
 | 
			
		||||
                this._scanner.input, `The CSS "at" rule "${tokenName}" is not allowed to used here`,
 | 
			
		||||
                token.strValue, token.index, token.line, token.column),
 | 
			
		||||
            token);
 | 
			
		||||
 | 
			
		||||
        this._collectUntilDelim(delimiters | LBRACE_DELIM_FLAG | SEMICOLON_DELIM_FLAG)
 | 
			
		||||
@ -336,9 +292,8 @@ export class CssParser {
 | 
			
		||||
              .forEach((token) => { listOfTokens.push(token); });
 | 
			
		||||
          listOfTokens.push(this._consume(CssTokenType.Character, '}'));
 | 
			
		||||
        }
 | 
			
		||||
        var endToken = listOfTokens[listOfTokens.length - 1];
 | 
			
		||||
        var span = this._generateSourceSpan(startToken, endToken);
 | 
			
		||||
        return new CssUnknownRuleAst(span, tokenName, listOfTokens);
 | 
			
		||||
        end = this._getScannerIndex() - 1;
 | 
			
		||||
        return new CssUnknownRuleAst(start, end, tokenName, listOfTokens);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -347,27 +302,23 @@ export class CssParser {
 | 
			
		||||
    const start = this._getScannerIndex();
 | 
			
		||||
    var selectors = this._parseSelectors(delimiters);
 | 
			
		||||
    var block = this._parseStyleBlock(delimiters);
 | 
			
		||||
    var ruleAst: CssRuleAst;
 | 
			
		||||
    var span: ParseSourceSpan;
 | 
			
		||||
    var startSelector = selectors[0];
 | 
			
		||||
    const end = this._getScannerIndex() - 1;
 | 
			
		||||
    var token: CssRuleAst;
 | 
			
		||||
    if (isPresent(block)) {
 | 
			
		||||
      var span = this._generateSourceSpan(startSelector, block);
 | 
			
		||||
      ruleAst = new CssSelectorRuleAst(span, selectors, block);
 | 
			
		||||
      token = new CssSelectorRuleAst(start, end, selectors, block);
 | 
			
		||||
    } else {
 | 
			
		||||
      var name = this._extractSourceContent(start, this._getScannerIndex() - 1);
 | 
			
		||||
      var name = this._scanner.input.substring(start, end);
 | 
			
		||||
      var innerTokens: CssToken[] = [];
 | 
			
		||||
      selectors.forEach((selector: CssSelectorAst) => {
 | 
			
		||||
        selector.selectorParts.forEach((part: CssSimpleSelectorAst) => {
 | 
			
		||||
          part.tokens.forEach((token: CssToken) => { innerTokens.push(token); });
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
      var endToken = innerTokens[innerTokens.length - 1];
 | 
			
		||||
      span = this._generateSourceSpan(startSelector, endToken);
 | 
			
		||||
      ruleAst = new CssUnknownTokenListAst(span, name, innerTokens);
 | 
			
		||||
      token = new CssUnknownTokenListAst(start, end, name, innerTokens);
 | 
			
		||||
    }
 | 
			
		||||
    this._scanner.setMode(CssLexerMode.BLOCK);
 | 
			
		||||
    this._scanner.consumeEmptyStatements();
 | 
			
		||||
    return ruleAst;
 | 
			
		||||
    return token;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
@ -401,7 +352,6 @@ export class CssParser {
 | 
			
		||||
    if (isPresent(error)) {
 | 
			
		||||
      this._error(error.rawMessage, token);
 | 
			
		||||
    }
 | 
			
		||||
    this._lastToken = token;
 | 
			
		||||
    return token;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -416,26 +366,27 @@ export class CssParser {
 | 
			
		||||
    if (isPresent(error)) {
 | 
			
		||||
      this._error(error.rawMessage, token);
 | 
			
		||||
    }
 | 
			
		||||
    this._lastToken = token;
 | 
			
		||||
    return token;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
  _parseKeyframeBlock(delimiters: number): CssBlockAst {
 | 
			
		||||
    const start = this._getScannerIndex();
 | 
			
		||||
 | 
			
		||||
    delimiters |= RBRACE_DELIM_FLAG;
 | 
			
		||||
    this._scanner.setMode(CssLexerMode.KEYFRAME_BLOCK);
 | 
			
		||||
 | 
			
		||||
    var startToken = this._consume(CssTokenType.Character, '{');
 | 
			
		||||
    this._consume(CssTokenType.Character, '{');
 | 
			
		||||
 | 
			
		||||
    var definitions: CssKeyframeDefinitionAst[] = [];
 | 
			
		||||
    while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
 | 
			
		||||
      definitions.push(this._parseKeyframeDefinition(delimiters));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var endToken = this._consume(CssTokenType.Character, '}');
 | 
			
		||||
    this._consume(CssTokenType.Character, '}');
 | 
			
		||||
 | 
			
		||||
    var span = this._generateSourceSpan(startToken, endToken);
 | 
			
		||||
    return new CssBlockAst(span, definitions);
 | 
			
		||||
    const end = this._getScannerIndex() - 1;
 | 
			
		||||
    return new CssBlockAst(start, end, definitions);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
@ -449,12 +400,10 @@ export class CssParser {
 | 
			
		||||
        this._consume(CssTokenType.Character, ',');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    var stylesBlock = this._parseStyleBlock(delimiters | RBRACE_DELIM_FLAG);
 | 
			
		||||
    var span = this._generateSourceSpan(stepTokens[0], stylesBlock);
 | 
			
		||||
    var ast = new CssKeyframeDefinitionAst(span, stepTokens, stylesBlock);
 | 
			
		||||
 | 
			
		||||
    var styles = this._parseStyleBlock(delimiters | RBRACE_DELIM_FLAG);
 | 
			
		||||
    this._scanner.setMode(CssLexerMode.BLOCK);
 | 
			
		||||
    return ast;
 | 
			
		||||
    const end = this._getScannerIndex() - 1;
 | 
			
		||||
    return new CssKeyframeDefinitionAst(start, end, stepTokens, styles);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
@ -476,7 +425,8 @@ export class CssParser {
 | 
			
		||||
    var tokens = [startToken];
 | 
			
		||||
 | 
			
		||||
    if (this._scanner.peek == chars.$COLON) {  // ::something
 | 
			
		||||
      tokens.push(this._consume(CssTokenType.Character, ':'));
 | 
			
		||||
      startToken = this._consume(CssTokenType.Character, ':');
 | 
			
		||||
      tokens.push(startToken);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var innerSelectors: CssSelectorAst[] = [];
 | 
			
		||||
@ -525,11 +475,9 @@ export class CssParser {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const end = this._getScannerIndex() - 1;
 | 
			
		||||
    var strValue = this._extractSourceContent(start, end);
 | 
			
		||||
 | 
			
		||||
    var endToken = tokens[tokens.length - 1];
 | 
			
		||||
    var span = this._generateSourceSpan(startToken, endToken);
 | 
			
		||||
    return new CssPseudoSelectorAst(span, strValue, pseudoSelectorName, tokens, innerSelectors);
 | 
			
		||||
    var strValue = this._scanner.input.substring(start, end);
 | 
			
		||||
    return new CssPseudoSelectorAst(
 | 
			
		||||
        start, end, strValue, pseudoSelectorName, tokens, innerSelectors);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
@ -599,11 +547,12 @@ export class CssParser {
 | 
			
		||||
          previousToken);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var end = this._getScannerIndex() - 1;
 | 
			
		||||
 | 
			
		||||
    // this happens if the selector is not directly followed by
 | 
			
		||||
    // a comma or curly brace without a space in between
 | 
			
		||||
    if (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
 | 
			
		||||
    var end = this._getScannerIndex();
 | 
			
		||||
    if (characterContainsDelimiter(this._scanner.peek, delimiters)) {
 | 
			
		||||
      // this happens if the selector is followed by a comma or curly
 | 
			
		||||
      // brace without a space in between
 | 
			
		||||
      end--;
 | 
			
		||||
    } else {
 | 
			
		||||
      var operator: CssToken = null;
 | 
			
		||||
      var operatorScanCount = 0;
 | 
			
		||||
      var lastOperatorToken: CssToken = null;
 | 
			
		||||
@ -631,7 +580,7 @@ export class CssParser {
 | 
			
		||||
                let text = SLASH_CHARACTER + deepToken.strValue + deepSlash.strValue;
 | 
			
		||||
                this._error(
 | 
			
		||||
                    generateErrorMessage(
 | 
			
		||||
                        this._getSourceContent(), `${text} is an invalid CSS operator`, text, index,
 | 
			
		||||
                        this._scanner.input, `${text} is an invalid CSS operator`, text, index,
 | 
			
		||||
                        line, column),
 | 
			
		||||
                    lastOperatorToken);
 | 
			
		||||
                token = new CssToken(index, column, line, CssTokenType.Invalid, text);
 | 
			
		||||
@ -651,21 +600,13 @@ export class CssParser {
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          operator = token;
 | 
			
		||||
          end = this._getScannerIndex() - 1;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // so long as there is an operator then we can have an
 | 
			
		||||
      // ending value that is beyond the selector value ...
 | 
			
		||||
      // otherwise it's just a bunch of trailing whitespace
 | 
			
		||||
      if (isPresent(operator)) {
 | 
			
		||||
        end = operator.index;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this._scanner.consumeWhitespace();
 | 
			
		||||
 | 
			
		||||
    var strValue = this._extractSourceContent(start, end);
 | 
			
		||||
 | 
			
		||||
    // if we do come across one or more spaces inside of
 | 
			
		||||
    // the operators loop then an empty space is still a
 | 
			
		||||
    // valid operator to use if something else was not found
 | 
			
		||||
@ -673,42 +614,33 @@ export class CssParser {
 | 
			
		||||
      operator = lastOperatorToken;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // please note that `endToken` is reassigned multiple times below
 | 
			
		||||
    // so please do not optimize the if statements into if/elseif
 | 
			
		||||
    var startTokenOrAst: CssToken|CssAst = null;
 | 
			
		||||
    var endTokenOrAst: CssToken|CssAst = null;
 | 
			
		||||
    if (selectorCssTokens.length > 0) {
 | 
			
		||||
      startTokenOrAst = startTokenOrAst || selectorCssTokens[0];
 | 
			
		||||
      endTokenOrAst = selectorCssTokens[selectorCssTokens.length - 1];
 | 
			
		||||
    }
 | 
			
		||||
    if (pseudoSelectors.length > 0) {
 | 
			
		||||
      startTokenOrAst = startTokenOrAst || pseudoSelectors[0];
 | 
			
		||||
      endTokenOrAst = pseudoSelectors[pseudoSelectors.length - 1];
 | 
			
		||||
    }
 | 
			
		||||
    if (isPresent(operator)) {
 | 
			
		||||
      startTokenOrAst = startTokenOrAst || operator;
 | 
			
		||||
      endTokenOrAst = operator;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var span = this._generateSourceSpan(startTokenOrAst, endTokenOrAst);
 | 
			
		||||
    return new CssSimpleSelectorAst(span, selectorCssTokens, strValue, pseudoSelectors, operator);
 | 
			
		||||
    var strValue = this._scanner.input.substring(start, end);
 | 
			
		||||
    return new CssSimpleSelectorAst(
 | 
			
		||||
        start, end, selectorCssTokens, strValue, pseudoSelectors, operator);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
  _parseSelector(delimiters: number): CssSelectorAst {
 | 
			
		||||
    const start = this._getScannerIndex();
 | 
			
		||||
 | 
			
		||||
    delimiters |= COMMA_DELIM_FLAG;
 | 
			
		||||
    this._scanner.setMode(CssLexerMode.SELECTOR);
 | 
			
		||||
 | 
			
		||||
    var simpleSelectors: CssSimpleSelectorAst[] = [];
 | 
			
		||||
    var end = this._getScannerIndex() - 1;
 | 
			
		||||
    while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
 | 
			
		||||
      simpleSelectors.push(this._parseSimpleSelector(delimiters));
 | 
			
		||||
      this._scanner.consumeWhitespace();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var firstSelector = simpleSelectors[0];
 | 
			
		||||
    var lastSelector = simpleSelectors[simpleSelectors.length - 1];
 | 
			
		||||
    var span = this._generateSourceSpan(firstSelector, lastSelector);
 | 
			
		||||
    return new CssSelectorAst(span, simpleSelectors);
 | 
			
		||||
    // we do this to avoid any trailing whitespace that is processed
 | 
			
		||||
    // in order to determine the final operator value
 | 
			
		||||
    var limit = simpleSelectors.length - 1;
 | 
			
		||||
    if (limit >= 0) {
 | 
			
		||||
      end = simpleSelectors[limit].end;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return new CssSelectorAst(start, end, simpleSelectors);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
@ -758,16 +690,13 @@ export class CssParser {
 | 
			
		||||
    } else if (code != chars.$RBRACE) {
 | 
			
		||||
      this._error(
 | 
			
		||||
          generateErrorMessage(
 | 
			
		||||
              this._getSourceContent(), `The CSS key/value definition did not end with a semicolon`,
 | 
			
		||||
              this._scanner.input, `The CSS key/value definition did not end with a semicolon`,
 | 
			
		||||
              previous.strValue, previous.index, previous.line, previous.column),
 | 
			
		||||
          previous);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var strValue = this._extractSourceContent(start, end);
 | 
			
		||||
    var startToken = tokens[0];
 | 
			
		||||
    var endToken = tokens[tokens.length - 1];
 | 
			
		||||
    var span = this._generateSourceSpan(startToken, endToken);
 | 
			
		||||
    return new CssStyleValueAst(span, tokens, strValue);
 | 
			
		||||
    var strValue = this._scanner.input.substring(start, end);
 | 
			
		||||
    return new CssStyleValueAst(start, end, tokens, strValue);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
@ -782,35 +711,39 @@ export class CssParser {
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
  _parseBlock(delimiters: number): CssBlockAst {
 | 
			
		||||
    const start = this._getScannerIndex();
 | 
			
		||||
 | 
			
		||||
    delimiters |= RBRACE_DELIM_FLAG;
 | 
			
		||||
 | 
			
		||||
    this._scanner.setMode(CssLexerMode.BLOCK);
 | 
			
		||||
 | 
			
		||||
    var startToken = this._consume(CssTokenType.Character, '{');
 | 
			
		||||
    this._consume(CssTokenType.Character, '{');
 | 
			
		||||
    this._scanner.consumeEmptyStatements();
 | 
			
		||||
 | 
			
		||||
    var results: CssRuleAst[] = [];
 | 
			
		||||
    var results: CssAst[] = [];
 | 
			
		||||
    while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
 | 
			
		||||
      results.push(this._parseRule(delimiters));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var endToken = this._consume(CssTokenType.Character, '}');
 | 
			
		||||
    this._consume(CssTokenType.Character, '}');
 | 
			
		||||
 | 
			
		||||
    this._scanner.setMode(CssLexerMode.BLOCK);
 | 
			
		||||
    this._scanner.consumeEmptyStatements();
 | 
			
		||||
 | 
			
		||||
    var span = this._generateSourceSpan(startToken, endToken);
 | 
			
		||||
    return new CssBlockAst(span, results);
 | 
			
		||||
    const end = this._getScannerIndex() - 1;
 | 
			
		||||
    return new CssBlockAst(start, end, results);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
  _parseStyleBlock(delimiters: number): CssStylesBlockAst {
 | 
			
		||||
    const start = this._getScannerIndex();
 | 
			
		||||
 | 
			
		||||
    delimiters |= RBRACE_DELIM_FLAG | LBRACE_DELIM_FLAG;
 | 
			
		||||
 | 
			
		||||
    this._scanner.setMode(CssLexerMode.STYLE_BLOCK);
 | 
			
		||||
 | 
			
		||||
    var startToken = this._consume(CssTokenType.Character, '{');
 | 
			
		||||
    if (startToken.numValue != chars.$LBRACE) {
 | 
			
		||||
    var result = this._consume(CssTokenType.Character, '{');
 | 
			
		||||
    if (result.numValue != chars.$LBRACE) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -822,28 +755,32 @@ export class CssParser {
 | 
			
		||||
      this._scanner.consumeEmptyStatements();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var endToken = this._consume(CssTokenType.Character, '}');
 | 
			
		||||
    this._consume(CssTokenType.Character, '}');
 | 
			
		||||
 | 
			
		||||
    this._scanner.setMode(CssLexerMode.STYLE_BLOCK);
 | 
			
		||||
    this._scanner.consumeEmptyStatements();
 | 
			
		||||
 | 
			
		||||
    var span = this._generateSourceSpan(startToken, endToken);
 | 
			
		||||
    return new CssStylesBlockAst(span, definitions);
 | 
			
		||||
    const end = this._getScannerIndex() - 1;
 | 
			
		||||
    return new CssStylesBlockAst(start, end, definitions);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
  _parseDefinition(delimiters: number): CssDefinitionAst {
 | 
			
		||||
    const start = this._getScannerIndex();
 | 
			
		||||
    this._scanner.setMode(CssLexerMode.STYLE_BLOCK);
 | 
			
		||||
 | 
			
		||||
    var prop = this._consume(CssTokenType.Identifier);
 | 
			
		||||
    var parseValue: boolean = false;
 | 
			
		||||
    var value: CssStyleValueAst = null;
 | 
			
		||||
    var endToken: CssToken|CssStyleValueAst = prop;
 | 
			
		||||
    var parseValue: boolean, value: CssStyleValueAst = null;
 | 
			
		||||
 | 
			
		||||
    // the colon value separates the prop from the style.
 | 
			
		||||
    // there are a few cases as to what could happen if it
 | 
			
		||||
    // is missing
 | 
			
		||||
    switch (this._scanner.peek) {
 | 
			
		||||
      case chars.$COLON:
 | 
			
		||||
        this._consume(CssTokenType.Character, ':');
 | 
			
		||||
        parseValue = true;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case chars.$SEMICOLON:
 | 
			
		||||
      case chars.$RBRACE:
 | 
			
		||||
      case chars.$EOF:
 | 
			
		||||
@ -863,31 +800,31 @@ export class CssParser {
 | 
			
		||||
            remainingTokens.forEach((token) => { propStr.push(token.strValue); });
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          endToken = prop =
 | 
			
		||||
              new CssToken(prop.index, prop.column, prop.line, prop.type, propStr.join(' '));
 | 
			
		||||
          prop = new CssToken(prop.index, prop.column, prop.line, prop.type, propStr.join(' '));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // this means we've reached the end of the definition and/or block
 | 
			
		||||
        if (this._scanner.peek == chars.$COLON) {
 | 
			
		||||
          this._consume(CssTokenType.Character, ':');
 | 
			
		||||
          parseValue = true;
 | 
			
		||||
        } else {
 | 
			
		||||
          parseValue = false;
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (parseValue) {
 | 
			
		||||
      value = this._parseValue(delimiters);
 | 
			
		||||
      endToken = value;
 | 
			
		||||
    } else {
 | 
			
		||||
      this._error(
 | 
			
		||||
          generateErrorMessage(
 | 
			
		||||
              this._getSourceContent(), `The CSS property was not paired with a style value`,
 | 
			
		||||
              this._scanner.input, `The CSS property was not paired with a style value`,
 | 
			
		||||
              prop.strValue, prop.index, prop.line, prop.column),
 | 
			
		||||
          prop);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var span = this._generateSourceSpan(prop, endToken);
 | 
			
		||||
    return new CssDefinitionAst(span, prop, value);
 | 
			
		||||
    const end = this._getScannerIndex() - 1;
 | 
			
		||||
    return new CssDefinitionAst(start, end, prop, value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @internal */
 | 
			
		||||
@ -908,15 +845,203 @@ export class CssParser {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssStyleValueAst extends CssAst {
 | 
			
		||||
  constructor(start: number, end: number, public tokens: CssToken[], public strValue: string) {
 | 
			
		||||
    super(start, end);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any { return visitor.visitCssValue(this); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export abstract class CssRuleAst extends CssAst {
 | 
			
		||||
  constructor(start: number, end: number) { super(start, end); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssBlockRuleAst extends CssRuleAst {
 | 
			
		||||
  constructor(
 | 
			
		||||
      start: number, end: number, public type: BlockType, public block: CssBlockAst,
 | 
			
		||||
      public name: CssToken = null) {
 | 
			
		||||
    super(start, end);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssBlock(this.block, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssKeyframeRuleAst extends CssBlockRuleAst {
 | 
			
		||||
  constructor(start: number, end: number, name: CssToken, block: CssBlockAst) {
 | 
			
		||||
    super(start, end, BlockType.Keyframes, block, name);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssKeyframeRule(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssKeyframeDefinitionAst extends CssBlockRuleAst {
 | 
			
		||||
  public steps: CssToken[];
 | 
			
		||||
  constructor(start: number, end: number, _steps: CssToken[], block: CssBlockAst) {
 | 
			
		||||
    super(start, end, BlockType.Keyframes, block, mergeTokens(_steps, ','));
 | 
			
		||||
    this.steps = _steps;
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssKeyframeDefinition(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssBlockDefinitionRuleAst extends CssBlockRuleAst {
 | 
			
		||||
  constructor(
 | 
			
		||||
      start: number, end: number, public strValue: string, type: BlockType,
 | 
			
		||||
      public query: CssAtRulePredicateAst, block: CssBlockAst) {
 | 
			
		||||
    super(start, end, type, block);
 | 
			
		||||
    var firstCssToken: CssToken = query.tokens[0];
 | 
			
		||||
    this.name = new CssToken(
 | 
			
		||||
        firstCssToken.index, firstCssToken.column, firstCssToken.line, CssTokenType.Identifier,
 | 
			
		||||
        this.strValue);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssBlock(this.block, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssMediaQueryRuleAst extends CssBlockDefinitionRuleAst {
 | 
			
		||||
  constructor(
 | 
			
		||||
      start: number, end: number, strValue: string, query: CssAtRulePredicateAst,
 | 
			
		||||
      block: CssBlockAst) {
 | 
			
		||||
    super(start, end, strValue, BlockType.MediaQuery, query, block);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssMediaQueryRule(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssAtRulePredicateAst extends CssAst {
 | 
			
		||||
  constructor(start: number, end: number, public strValue: string, public tokens: CssToken[]) {
 | 
			
		||||
    super(start, end);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssAtRulePredicate(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssInlineRuleAst extends CssRuleAst {
 | 
			
		||||
  constructor(start: number, end: number, public type: BlockType, public value: CssStyleValueAst) {
 | 
			
		||||
    super(start, end);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssInlineRule(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssSelectorRuleAst extends CssBlockRuleAst {
 | 
			
		||||
  public strValue: string;
 | 
			
		||||
 | 
			
		||||
  constructor(start: number, end: number, public selectors: CssSelectorAst[], block: CssBlockAst) {
 | 
			
		||||
    super(start, end, BlockType.Selector, block);
 | 
			
		||||
    this.strValue = selectors.map(selector => selector.strValue).join(',');
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssSelectorRule(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssDefinitionAst extends CssAst {
 | 
			
		||||
  constructor(
 | 
			
		||||
      start: number, end: number, public property: CssToken, public value: CssStyleValueAst) {
 | 
			
		||||
    super(start, end);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssDefinition(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export abstract class CssSelectorPartAst extends CssAst {
 | 
			
		||||
  constructor(start: number, end: number) { super(start, end); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssSelectorAst extends CssSelectorPartAst {
 | 
			
		||||
  public strValue: string;
 | 
			
		||||
  constructor(start: number, end: number, public selectorParts: CssSimpleSelectorAst[]) {
 | 
			
		||||
    super(start, end);
 | 
			
		||||
    this.strValue = selectorParts.map(part => part.strValue).join('');
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssSelector(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssSimpleSelectorAst extends CssSelectorPartAst {
 | 
			
		||||
  constructor(
 | 
			
		||||
      start: number, end: number, public tokens: CssToken[], public strValue: string,
 | 
			
		||||
      public pseudoSelectors: CssPseudoSelectorAst[], public operator: CssToken) {
 | 
			
		||||
    super(start, end);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssSimpleSelector(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssPseudoSelectorAst extends CssSelectorPartAst {
 | 
			
		||||
  constructor(
 | 
			
		||||
      start: number, end: number, public strValue: string, public name: string,
 | 
			
		||||
      public tokens: CssToken[], public innerSelectors: CssSelectorAst[]) {
 | 
			
		||||
    super(start, end);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssPseudoSelector(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssBlockAst extends CssAst {
 | 
			
		||||
  constructor(start: number, end: number, public entries: CssAst[]) { super(start, end); }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any { return visitor.visitCssBlock(this, context); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
  a style block is different from a standard block because it contains
 | 
			
		||||
  css prop:value definitions. A regular block can contain a list of Ast entries.
 | 
			
		||||
 */
 | 
			
		||||
export class CssStylesBlockAst extends CssBlockAst {
 | 
			
		||||
  constructor(start: number, end: number, public definitions: CssDefinitionAst[]) {
 | 
			
		||||
    super(start, end, definitions);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssStylesBlock(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssStyleSheetAst extends CssAst {
 | 
			
		||||
  constructor(start: number, end: number, public rules: CssAst[]) { super(start, end); }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssStyleSheet(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssParseError extends ParseError {
 | 
			
		||||
  static create(
 | 
			
		||||
      file: ParseSourceFile, offset: number, line: number, col: number, length: number,
 | 
			
		||||
      errMsg: string): CssParseError {
 | 
			
		||||
    var start = new ParseLocation(file, offset, line, col);
 | 
			
		||||
    var end = new ParseLocation(file, offset, line, col + length);
 | 
			
		||||
    const end = new ParseLocation(file, offset, line, col + length);
 | 
			
		||||
    var span = new ParseSourceSpan(start, end);
 | 
			
		||||
    return new CssParseError(span, 'CSS Parse Error: ' + errMsg);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  constructor(span: ParseSourceSpan, message: string) { super(span, message); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssUnknownRuleAst extends CssRuleAst {
 | 
			
		||||
  constructor(start: number, end: number, public ruleName: string, public tokens: CssToken[]) {
 | 
			
		||||
    super(start, end);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssUnknownRule(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CssUnknownTokenListAst extends CssRuleAst {
 | 
			
		||||
  constructor(start: number, end: number, public name: string, public tokens: CssToken[]) {
 | 
			
		||||
    super(start, end);
 | 
			
		||||
  }
 | 
			
		||||
  visit(visitor: CssAstVisitor, context?: any): any {
 | 
			
		||||
    return visitor.visitCssUnknownTokenList(this, context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -7,12 +7,11 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '../../core/testing/testing_internal';
 | 
			
		||||
import {CssBlockAst, CssBlockDefinitionRuleAst, CssBlockRuleAst, CssDefinitionAst, CssInlineRuleAst, CssKeyframeDefinitionAst, CssKeyframeRuleAst, CssMediaQueryRuleAst, CssRuleAst, CssSelectorAst, CssSelectorRuleAst, CssStyleSheetAst, CssStyleValueAst} from '../src/css_ast';
 | 
			
		||||
import {BlockType, CssParseError, CssParser, CssToken, ParsedCssResult} from '../src/css_parser';
 | 
			
		||||
import {CssLexer} from '../src/css_lexer';
 | 
			
		||||
import {BlockType, CssBlockAst, CssBlockDefinitionRuleAst, CssBlockRuleAst, CssDefinitionAst, CssInlineRuleAst, CssKeyframeDefinitionAst, CssKeyframeRuleAst, CssMediaQueryRuleAst, CssParseError, CssParser, CssRuleAst, CssSelectorAst, CssSelectorRuleAst, CssStyleSheetAst, CssStyleValueAst, ParsedCssResult} from '../src/css_parser';
 | 
			
		||||
import {BaseException} from '../src/facade/exceptions';
 | 
			
		||||
import {ParseLocation} from '../src/parse_util';
 | 
			
		||||
 | 
			
		||||
export function assertTokens(tokens: CssToken[], valuesArr: string[]) {
 | 
			
		||||
export function assertTokens(tokens: any /** TODO #9100 */, valuesArr: any /** TODO #9100 */) {
 | 
			
		||||
  for (var i = 0; i < tokens.length; i++) {
 | 
			
		||||
    expect(tokens[i].strValue == valuesArr[i]);
 | 
			
		||||
  }
 | 
			
		||||
@ -20,11 +19,14 @@ export function assertTokens(tokens: CssToken[], valuesArr: string[]) {
 | 
			
		||||
 | 
			
		||||
export function main() {
 | 
			
		||||
  describe('CssParser', () => {
 | 
			
		||||
    function parse(css: string): ParsedCssResult {
 | 
			
		||||
      return new CssParser().parse(css, 'some-fake-css-file.css');
 | 
			
		||||
    function parse(css: any /** TODO #9100 */): ParsedCssResult {
 | 
			
		||||
      var lexer = new CssLexer();
 | 
			
		||||
      var scanner = lexer.scan(css);
 | 
			
		||||
      var parser = new CssParser(scanner, 'some-fake-file-name.css');
 | 
			
		||||
      return parser.parse();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function makeAst(css: string): CssStyleSheetAst {
 | 
			
		||||
    function makeAst(css: any /** TODO #9100 */): CssStyleSheetAst {
 | 
			
		||||
      var output = parse(css);
 | 
			
		||||
      var errors = output.errors;
 | 
			
		||||
      if (errors.length > 0) {
 | 
			
		||||
@ -34,7 +36,11 @@ export function main() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    it('should parse CSS into a stylesheet Ast', () => {
 | 
			
		||||
      var styles = '.selector { prop: value123; }';
 | 
			
		||||
      var styles = `
 | 
			
		||||
        .selector {
 | 
			
		||||
          prop: value123;
 | 
			
		||||
        }
 | 
			
		||||
      `;
 | 
			
		||||
 | 
			
		||||
      var ast = makeAst(styles);
 | 
			
		||||
      expect(ast.rules.length).toEqual(1);
 | 
			
		||||
@ -149,7 +155,7 @@ export function main() {
 | 
			
		||||
      expect(ast.rules.length).toEqual(1);
 | 
			
		||||
 | 
			
		||||
      var rule = <CssMediaQueryRuleAst>ast.rules[0];
 | 
			
		||||
      assertTokens(rule.query.tokens, ['all', 'and', '(', 'max-width', ':', '100', 'px', ')']);
 | 
			
		||||
      assertTokens(rule.query, ['all', 'and', '(', 'max-width', ':', '100', 'px', ')']);
 | 
			
		||||
 | 
			
		||||
      var block = <CssBlockAst>rule.block;
 | 
			
		||||
      expect(block.entries.length).toEqual(1);
 | 
			
		||||
@ -262,8 +268,7 @@ export function main() {
 | 
			
		||||
      expect(fontFaceRule.block.entries.length).toEqual(2);
 | 
			
		||||
 | 
			
		||||
      var mediaQueryRule = <CssMediaQueryRuleAst>ast.rules[2];
 | 
			
		||||
      assertTokens(
 | 
			
		||||
          mediaQueryRule.query.tokens, ['all', 'and', '(', 'max-width', ':', '100', 'px', ')']);
 | 
			
		||||
      assertTokens(mediaQueryRule.query, ['all', 'and', '(', 'max-width', ':', '100', 'px', ')']);
 | 
			
		||||
      expect(mediaQueryRule.block.entries.length).toEqual(2);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -367,7 +372,7 @@ export function main() {
 | 
			
		||||
      var rules = ast.rules;
 | 
			
		||||
 | 
			
		||||
      var supportsRule = <CssBlockDefinitionRuleAst>rules[0];
 | 
			
		||||
      assertTokens(supportsRule.query.tokens, ['(', 'animation-name', ':', 'rotate', ')']);
 | 
			
		||||
      assertTokens(supportsRule.query, ['(', 'animation-name', ':', 'rotate', ')']);
 | 
			
		||||
      expect(supportsRule.type).toEqual(BlockType.Supports);
 | 
			
		||||
 | 
			
		||||
      var selectorOne = <CssSelectorRuleAst>supportsRule.block.entries[0];
 | 
			
		||||
@ -557,148 +562,6 @@ export function main() {
 | 
			
		||||
      assertTokens(style2.value.tokens, ['white']);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('location offsets', () => {
 | 
			
		||||
      var styles: string;
 | 
			
		||||
 | 
			
		||||
      function assertMatchesOffsetAndChar(
 | 
			
		||||
          location: ParseLocation, expectedOffset: number, expectedChar: string): void {
 | 
			
		||||
        expect(location.offset).toEqual(expectedOffset);
 | 
			
		||||
        expect(styles[expectedOffset]).toEqual(expectedChar);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      it('should collect the source span location of each AST node with regular selectors', () => {
 | 
			
		||||
        styles = '.problem-class { border-top-right: 1px; color: white; }\n';
 | 
			
		||||
        styles += '#good-boy-rule_ { background-color: #fe4; color: teal; }';
 | 
			
		||||
 | 
			
		||||
        var output = parse(styles);
 | 
			
		||||
        var ast = output.ast;
 | 
			
		||||
        assertMatchesOffsetAndChar(ast.location.start, 0, '.');
 | 
			
		||||
        assertMatchesOffsetAndChar(ast.location.end, 111, '}');
 | 
			
		||||
 | 
			
		||||
        var rule1 = <CssSelectorRuleAst>ast.rules[0];
 | 
			
		||||
        assertMatchesOffsetAndChar(rule1.location.start, 0, '.');
 | 
			
		||||
        assertMatchesOffsetAndChar(rule1.location.end, 54, '}');
 | 
			
		||||
 | 
			
		||||
        var rule2 = <CssSelectorRuleAst>ast.rules[1];
 | 
			
		||||
        assertMatchesOffsetAndChar(rule2.location.start, 56, '#');
 | 
			
		||||
        assertMatchesOffsetAndChar(rule2.location.end, 111, '}');
 | 
			
		||||
 | 
			
		||||
        var selector1 = rule1.selectors[0];
 | 
			
		||||
        assertMatchesOffsetAndChar(selector1.location.start, 0, '.');
 | 
			
		||||
        assertMatchesOffsetAndChar(selector1.location.end, 1, 'p');  // problem-class
 | 
			
		||||
 | 
			
		||||
        var selector2 = rule2.selectors[0];
 | 
			
		||||
        assertMatchesOffsetAndChar(selector2.location.start, 56, '#');
 | 
			
		||||
        assertMatchesOffsetAndChar(selector2.location.end, 57, 'g');  // good-boy-rule_
 | 
			
		||||
 | 
			
		||||
        var block1 = rule1.block;
 | 
			
		||||
        assertMatchesOffsetAndChar(block1.location.start, 15, '{');
 | 
			
		||||
        assertMatchesOffsetAndChar(block1.location.end, 54, '}');
 | 
			
		||||
 | 
			
		||||
        var block2 = rule2.block;
 | 
			
		||||
        assertMatchesOffsetAndChar(block2.location.start, 72, '{');
 | 
			
		||||
        assertMatchesOffsetAndChar(block2.location.end, 111, '}');
 | 
			
		||||
 | 
			
		||||
        var block1def1 = <CssDefinitionAst>block1.entries[0];
 | 
			
		||||
        assertMatchesOffsetAndChar(block1def1.location.start, 17, 'b');  // border-top-right
 | 
			
		||||
        assertMatchesOffsetAndChar(block1def1.location.end, 36, 'p');    // px
 | 
			
		||||
 | 
			
		||||
        var block1def2 = <CssDefinitionAst>block1.entries[1];
 | 
			
		||||
        assertMatchesOffsetAndChar(block1def2.location.start, 40, 'c');  // color
 | 
			
		||||
        assertMatchesOffsetAndChar(block1def2.location.end, 47, 'w');    // white
 | 
			
		||||
 | 
			
		||||
        var block2def1 = <CssDefinitionAst>block2.entries[0];
 | 
			
		||||
        assertMatchesOffsetAndChar(block2def1.location.start, 74, 'b');  // background-color
 | 
			
		||||
        assertMatchesOffsetAndChar(block2def1.location.end, 93, 'f');    // fe4
 | 
			
		||||
 | 
			
		||||
        var block2def2 = <CssDefinitionAst>block2.entries[1];
 | 
			
		||||
        assertMatchesOffsetAndChar(block2def2.location.start, 98, 'c');  // color
 | 
			
		||||
        assertMatchesOffsetAndChar(block2def2.location.end, 105, 't');   // teal
 | 
			
		||||
 | 
			
		||||
        var block1value1 = block1def1.value;
 | 
			
		||||
        assertMatchesOffsetAndChar(block1value1.location.start, 35, '1');
 | 
			
		||||
        assertMatchesOffsetAndChar(block1value1.location.end, 36, 'p');
 | 
			
		||||
 | 
			
		||||
        var block1value2 = block1def2.value;
 | 
			
		||||
        assertMatchesOffsetAndChar(block1value2.location.start, 47, 'w');
 | 
			
		||||
        assertMatchesOffsetAndChar(block1value2.location.end, 47, 'w');
 | 
			
		||||
 | 
			
		||||
        var block2value1 = block2def1.value;
 | 
			
		||||
        assertMatchesOffsetAndChar(block2value1.location.start, 92, '#');
 | 
			
		||||
        assertMatchesOffsetAndChar(block2value1.location.end, 93, 'f');
 | 
			
		||||
 | 
			
		||||
        var block2value2 = block2def2.value;
 | 
			
		||||
        assertMatchesOffsetAndChar(block2value2.location.start, 105, 't');
 | 
			
		||||
        assertMatchesOffsetAndChar(block2value2.location.end, 105, 't');
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('should collect the source span location of each AST node with media query data', () => {
 | 
			
		||||
        styles = '@media (all and max-width: 100px) { a { display:none; } }';
 | 
			
		||||
 | 
			
		||||
        var output = parse(styles);
 | 
			
		||||
        var ast = output.ast;
 | 
			
		||||
 | 
			
		||||
        var mediaQuery = <CssMediaQueryRuleAst>ast.rules[0];
 | 
			
		||||
        assertMatchesOffsetAndChar(mediaQuery.location.start, 0, '@');
 | 
			
		||||
        assertMatchesOffsetAndChar(mediaQuery.location.end, 56, '}');
 | 
			
		||||
 | 
			
		||||
        var predicate = mediaQuery.query;
 | 
			
		||||
        assertMatchesOffsetAndChar(predicate.location.start, 0, '@');
 | 
			
		||||
        assertMatchesOffsetAndChar(predicate.location.end, 32, ')');
 | 
			
		||||
 | 
			
		||||
        var rule = <CssSelectorRuleAst>mediaQuery.block.entries[0];
 | 
			
		||||
        assertMatchesOffsetAndChar(rule.location.start, 36, 'a');
 | 
			
		||||
        assertMatchesOffsetAndChar(rule.location.end, 54, '}');
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('should collect the source span location of each AST node with keyframe data', () => {
 | 
			
		||||
        styles = '@keyframes rotateAndZoomOut { ';
 | 
			
		||||
        styles += 'from { transform: rotate(0deg); } ';
 | 
			
		||||
        styles += '100% { transform: rotate(360deg) scale(2); }';
 | 
			
		||||
        styles += '}';
 | 
			
		||||
 | 
			
		||||
        var output = parse(styles);
 | 
			
		||||
        var ast = output.ast;
 | 
			
		||||
 | 
			
		||||
        var keyframes = <CssKeyframeRuleAst>ast.rules[0];
 | 
			
		||||
        assertMatchesOffsetAndChar(keyframes.location.start, 0, '@');
 | 
			
		||||
        assertMatchesOffsetAndChar(keyframes.location.end, 108, '}');
 | 
			
		||||
 | 
			
		||||
        var step1 = <CssKeyframeDefinitionAst>keyframes.block.entries[0];
 | 
			
		||||
        assertMatchesOffsetAndChar(step1.location.start, 30, 'f');
 | 
			
		||||
        assertMatchesOffsetAndChar(step1.location.end, 62, '}');
 | 
			
		||||
 | 
			
		||||
        var step2 = <CssKeyframeDefinitionAst>keyframes.block.entries[1];
 | 
			
		||||
        assertMatchesOffsetAndChar(step2.location.start, 64, '1');
 | 
			
		||||
        assertMatchesOffsetAndChar(step2.location.end, 107, '}');
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('should collect the source span location of each AST node with an inline rule', () => {
 | 
			
		||||
        styles = '@import url(something.css)';
 | 
			
		||||
 | 
			
		||||
        var output = parse(styles);
 | 
			
		||||
        var ast = output.ast;
 | 
			
		||||
 | 
			
		||||
        var rule = <CssInlineRuleAst>ast.rules[0];
 | 
			
		||||
        assertMatchesOffsetAndChar(rule.location.start, 0, '@');
 | 
			
		||||
        assertMatchesOffsetAndChar(rule.location.end, 25, ')');
 | 
			
		||||
 | 
			
		||||
        var value = rule.value;
 | 
			
		||||
        assertMatchesOffsetAndChar(value.location.start, 8, 'u');
 | 
			
		||||
        assertMatchesOffsetAndChar(value.location.end, 25, ')');
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('should property collect the start/end locations with an invalid stylesheet', () => {
 | 
			
		||||
        styles = '#id { something: value';
 | 
			
		||||
 | 
			
		||||
        var output = parse(styles);
 | 
			
		||||
        var ast = output.ast;
 | 
			
		||||
 | 
			
		||||
        assertMatchesOffsetAndChar(ast.location.start, 0, '#');
 | 
			
		||||
        assertMatchesOffsetAndChar(ast.location.end, 22, undefined);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should parse minified CSS content properly', () => {
 | 
			
		||||
      // this code was taken from the angular.io webpage's CSS code
 | 
			
		||||
      var styles = `
 | 
			
		||||
 | 
			
		||||
@ -7,10 +7,10 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '../../core/testing/testing_internal';
 | 
			
		||||
import {CssAst, CssAstVisitor, CssAtRulePredicateAst, CssBlockAst, CssBlockDefinitionRuleAst, CssBlockRuleAst, CssDefinitionAst, CssInlineRuleAst, CssKeyframeDefinitionAst, CssKeyframeRuleAst, CssMediaQueryRuleAst, CssPseudoSelectorAst, CssRuleAst, CssSelectorAst, CssSelectorRuleAst, CssSimpleSelectorAst, CssStyleSheetAst, CssStyleValueAst, CssStylesBlockAst, CssUnknownRuleAst, CssUnknownTokenListAst} from '../src/css_ast';
 | 
			
		||||
import {BlockType, CssParseError, CssParser, CssToken} from '../src/css_parser';
 | 
			
		||||
import {CssLexer} from '../src/css_lexer';
 | 
			
		||||
import {BlockType, CssAst, CssAstVisitor, CssAtRulePredicateAst, CssBlockAst, CssBlockDefinitionRuleAst, CssBlockRuleAst, CssDefinitionAst, CssInlineRuleAst, CssKeyframeDefinitionAst, CssKeyframeRuleAst, CssMediaQueryRuleAst, CssParseError, CssParser, CssPseudoSelectorAst, CssRuleAst, CssSelectorAst, CssSelectorRuleAst, CssSimpleSelectorAst, CssStyleSheetAst, CssStyleValueAst, CssStylesBlockAst, CssToken, CssUnknownRuleAst, CssUnknownTokenListAst} from '../src/css_parser';
 | 
			
		||||
import {BaseException} from '../src/facade/exceptions';
 | 
			
		||||
import {isPresent} from '../src/facade/lang';
 | 
			
		||||
import {NumberWrapper, StringWrapper, isPresent} from '../src/facade/lang';
 | 
			
		||||
 | 
			
		||||
function _assertTokens(tokens: CssToken[], valuesArr: string[]): void {
 | 
			
		||||
  expect(tokens.length).toEqual(valuesArr.length);
 | 
			
		||||
@ -115,7 +115,10 @@ function _getCaptureAst(capture: any[], index = 0): CssAst {
 | 
			
		||||
 | 
			
		||||
export function main() {
 | 
			
		||||
  function parse(cssCode: string, ignoreErrors: boolean = false) {
 | 
			
		||||
    var output = new CssParser().parse(cssCode, 'some-fake-css-file.css');
 | 
			
		||||
    var lexer = new CssLexer();
 | 
			
		||||
    var scanner = lexer.scan(cssCode);
 | 
			
		||||
    var parser = new CssParser(scanner, 'some-fake-file-name.css');
 | 
			
		||||
    var output = parser.parse();
 | 
			
		||||
    var errors = output.errors;
 | 
			
		||||
    if (errors.length > 0 && !ignoreErrors) {
 | 
			
		||||
      throw new BaseException(errors.map((error: CssParseError) => error.msg).join(', '));
 | 
			
		||||
@ -124,7 +127,7 @@ export function main() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  describe('CSS parsing and visiting', () => {
 | 
			
		||||
    var ast: CssStyleSheetAst;
 | 
			
		||||
    var ast: any /** TODO #9100 */;
 | 
			
		||||
    var context = {};
 | 
			
		||||
 | 
			
		||||
    beforeEach(() => {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user