From 74b45dfbf8808251a31dab010f607a45806b96f9 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Fri, 1 Jul 2016 21:21:56 -0700 Subject: [PATCH] Revert "refactor(core): ensure CSS parser uses ParseSourceSpan to track ast locations" This reverts commit 5c9f871b21c6ad9fce5ba05f013bc45fb0bde851. --- modules/@angular/compiler/src/css_ast.ts | 223 ------- modules/@angular/compiler/src/css_lexer.ts | 4 +- modules/@angular/compiler/src/css_parser.ts | 545 +++++++++++------- .../@angular/compiler/test/css_parser_spec.ts | 171 +----- .../compiler/test/css_visitor_spec.ts | 13 +- 5 files changed, 362 insertions(+), 594 deletions(-) delete mode 100644 modules/@angular/compiler/src/css_ast.ts diff --git a/modules/@angular/compiler/src/css_ast.ts b/modules/@angular/compiler/src/css_ast.ts deleted file mode 100644 index 97e4c304bf..0000000000 --- a/modules/@angular/compiler/src/css_ast.ts +++ /dev/null @@ -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); - } -} diff --git a/modules/@angular/compiler/src/css_lexer.ts b/modules/@angular/compiler/src/css_lexer.ts index 86c3f7e7a6..d6b9b0a7e1 100644 --- a/modules/@angular/compiler/src/css_lexer.ts +++ b/modules/@angular/compiler/src/css_lexer.ts @@ -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; diff --git a/modules/@angular/compiler/src/css_parser.ts b/modules/@angular/compiler/src/css_parser.ts index d5a6c68031..35a8f7c33d 100644 --- a/modules/@angular/compiler/src/css_parser.ts +++ b/modules/@angular/compiler/src/css_parser.ts @@ -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); + } +} diff --git a/modules/@angular/compiler/test/css_parser_spec.ts b/modules/@angular/compiler/test/css_parser_spec.ts index 1dc15fa2cf..4dacf6ddcc 100644 --- a/modules/@angular/compiler/test/css_parser_spec.ts +++ b/modules/@angular/compiler/test/css_parser_spec.ts @@ -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 = ast.rules[0]; - assertTokens(rule.query.tokens, ['all', 'and', '(', 'max-width', ':', '100', 'px', ')']); + assertTokens(rule.query, ['all', 'and', '(', 'max-width', ':', '100', 'px', ')']); var block = rule.block; expect(block.entries.length).toEqual(1); @@ -262,8 +268,7 @@ export function main() { expect(fontFaceRule.block.entries.length).toEqual(2); var mediaQueryRule = 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 = rules[0]; - assertTokens(supportsRule.query.tokens, ['(', 'animation-name', ':', 'rotate', ')']); + assertTokens(supportsRule.query, ['(', 'animation-name', ':', 'rotate', ')']); expect(supportsRule.type).toEqual(BlockType.Supports); var selectorOne = 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 = ast.rules[0]; - assertMatchesOffsetAndChar(rule1.location.start, 0, '.'); - assertMatchesOffsetAndChar(rule1.location.end, 54, '}'); - - var rule2 = 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 = block1.entries[0]; - assertMatchesOffsetAndChar(block1def1.location.start, 17, 'b'); // border-top-right - assertMatchesOffsetAndChar(block1def1.location.end, 36, 'p'); // px - - var block1def2 = block1.entries[1]; - assertMatchesOffsetAndChar(block1def2.location.start, 40, 'c'); // color - assertMatchesOffsetAndChar(block1def2.location.end, 47, 'w'); // white - - var block2def1 = block2.entries[0]; - assertMatchesOffsetAndChar(block2def1.location.start, 74, 'b'); // background-color - assertMatchesOffsetAndChar(block2def1.location.end, 93, 'f'); // fe4 - - var block2def2 = 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 = 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 = 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 = ast.rules[0]; - assertMatchesOffsetAndChar(keyframes.location.start, 0, '@'); - assertMatchesOffsetAndChar(keyframes.location.end, 108, '}'); - - var step1 = keyframes.block.entries[0]; - assertMatchesOffsetAndChar(step1.location.start, 30, 'f'); - assertMatchesOffsetAndChar(step1.location.end, 62, '}'); - - var step2 = 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 = 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 = ` diff --git a/modules/@angular/compiler/test/css_visitor_spec.ts b/modules/@angular/compiler/test/css_visitor_spec.ts index f2b630e71b..8ee43bc29a 100644 --- a/modules/@angular/compiler/test/css_visitor_spec.ts +++ b/modules/@angular/compiler/test/css_visitor_spec.ts @@ -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(() => {