From 127fbfd5a670ce3a9c12a77a178650a31eb8a5ef Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Fri, 11 Mar 2016 11:14:29 -0800 Subject: [PATCH] Revert "feat(core): introduce a CSS lexer/parser" This reverts commit 293fa5505b9f3d6ef8c3208f5dc8476243aaadc9. The rebased commit broke CI: https://travis-ci.org/angular/angular/jobs/115388814 --- modules/angular2/src/compiler/chars.ts | 64 -- modules/angular2/src/compiler/css/lexer.ts | 736 ------------------ modules/angular2/src/compiler/css/parser.ts | 702 ----------------- .../src/core/change_detection/parser/lexer.ts | 3 +- .../angular2/test/compiler/css/lexer_spec.ts | 386 --------- .../angular2/test/compiler/css/parser_spec.ts | 635 --------------- .../test/compiler/css/visitor_spec.ts | 262 ------- 7 files changed, 2 insertions(+), 2786 deletions(-) delete mode 100644 modules/angular2/src/compiler/chars.ts delete mode 100644 modules/angular2/src/compiler/css/lexer.ts delete mode 100644 modules/angular2/src/compiler/css/parser.ts delete mode 100644 modules/angular2/test/compiler/css/lexer_spec.ts delete mode 100644 modules/angular2/test/compiler/css/parser_spec.ts delete mode 100644 modules/angular2/test/compiler/css/visitor_spec.ts diff --git a/modules/angular2/src/compiler/chars.ts b/modules/angular2/src/compiler/chars.ts deleted file mode 100644 index 91c5ca4eff..0000000000 --- a/modules/angular2/src/compiler/chars.ts +++ /dev/null @@ -1,64 +0,0 @@ -export const $EOF = 0; -export const $TAB = 9; -export const $LF = 10; -export const $VTAB = 11; -export const $FF = 12; -export const $CR = 13; -export const $SPACE = 32; -export const $BANG = 33; -export const $DQ = 34; -export const $HASH = 35; -export const $$ = 36; -export const $PERCENT = 37; -export const $AMPERSAND = 38; -export const $SQ = 39; -export const $LPAREN = 40; -export const $RPAREN = 41; -export const $STAR = 42; -export const $PLUS = 43; -export const $COMMA = 44; -export const $MINUS = 45; -export const $PERIOD = 46; -export const $SLASH = 47; -export const $COLON = 58; -export const $SEMICOLON = 59; -export const $LT = 60; -export const $EQ = 61; -export const $GT = 62; -export const $QUESTION = 63; - -export const $0 = 48; -export const $9 = 57; - -export const $A = 65; -export const $E = 69; -export const $Z = 90; - -export const $LBRACKET = 91; -export const $BACKSLASH = 92; -export const $RBRACKET = 93; -export const $CARET = 94; -export const $_ = 95; - -export const $a = 97; -export const $e = 101; -export const $f = 102; -export const $n = 110; -export const $r = 114; -export const $t = 116; -export const $u = 117; -export const $v = 118; -export const $z = 122; - -export const $LBRACE = 123; -export const $BAR = 124; -export const $RBRACE = 125; -export const $NBSP = 160; - -export const $PIPE = 124; -export const $TILDA = 126; -export const $AT = 64; - -export function isWhitespace(code: number): boolean { - return (code >= $TAB && code <= $SPACE) || (code == $NBSP); -} diff --git a/modules/angular2/src/compiler/css/lexer.ts b/modules/angular2/src/compiler/css/lexer.ts deleted file mode 100644 index 798aa4ac44..0000000000 --- a/modules/angular2/src/compiler/css/lexer.ts +++ /dev/null @@ -1,736 +0,0 @@ -import {NumberWrapper, StringWrapper, isPresent} from "angular2/src/facade/lang"; -import {BaseException} from 'angular2/src/facade/exceptions'; - -import { - isWhitespace, - $EOF, - $HASH, - $TILDA, - $CARET, - $PERCENT, - $$, - $_, - $COLON, - $SQ, - $DQ, - $EQ, - $SLASH, - $BACKSLASH, - $PERIOD, - $STAR, - $PLUS, - $LPAREN, - $RPAREN, - $LBRACE, - $RBRACE, - $LBRACKET, - $RBRACKET, - $PIPE, - $COMMA, - $SEMICOLON, - $MINUS, - $BANG, - $QUESTION, - $AT, - $AMPERSAND, - $GT, - $a, - $A, - $z, - $Z, - $0, - $9, - $FF, - $CR, - $LF, - $VTAB -} from "angular2/src/compiler/chars"; - -export { - $EOF, - $AT, - $RBRACE, - $LBRACE, - $LBRACKET, - $RBRACKET, - $LPAREN, - $RPAREN, - $COMMA, - $COLON, - $SEMICOLON, - isWhitespace -} from "angular2/src/compiler/chars"; - -export enum CssTokenType { - EOF, - String, - Comment, - Identifier, - Number, - IdentifierOrNumber, - AtKeyword, - Character, - Whitespace, - Invalid -} - -export enum CssLexerMode { - ALL, - ALL_TRACK_WS, - SELECTOR, - PSEUDO_SELECTOR, - ATTRIBUTE_SELECTOR, - AT_RULE_QUERY, - MEDIA_QUERY, - BLOCK, - KEYFRAME_BLOCK, - STYLE_BLOCK, - STYLE_VALUE, - STYLE_VALUE_FUNCTION, - STYLE_CALC_FUNCTION -} - -export class LexedCssResult { - constructor(public error: CssScannerError, public token: CssToken) {} -} - -export function generateErrorMessage(input, message, errorValue, index, row, column) { - return `${message} at column ${row}:${column} in expression [` + - findProblemCode(input, errorValue, index, column) + ']'; -} - -export function findProblemCode(input, errorValue, index, column) { - var endOfProblemLine = index; - var current = charCode(input, index); - while (current > 0 && !isNewline(current)) { - current = charCode(input, ++endOfProblemLine); - } - var choppedString = input.substring(0, endOfProblemLine); - var pointerPadding = ""; - for (var i = 0; i < column; i++) { - pointerPadding += " "; - } - var pointerString = ""; - for (var i = 0; i < errorValue.length; i++) { - pointerString += "^"; - } - return choppedString + "\n" + pointerPadding + pointerString + "\n"; -} - -export class CssToken { - numValue: number; - constructor(public index: number, public column: number, public line: number, - public type: CssTokenType, public strValue: string) { - this.numValue = charCode(strValue, 0); - } -} - -export class CssLexer { - scan(text: string, trackComments: boolean = false): CssScanner { - return new CssScanner(text, trackComments); - } -} - -export class CssScannerError extends BaseException { - public rawMessage: string; - public message: string; - - constructor(public token: CssToken, message) { - super('Css Parse Error: ' + message); - this.rawMessage = message; - } - - toString(): string { return this.message; } -} - -function _trackWhitespace(mode: CssLexerMode) { - switch (mode) { - case CssLexerMode.SELECTOR: - case CssLexerMode.ALL_TRACK_WS: - case CssLexerMode.STYLE_VALUE: - return true; - } - return false; -} - -export class CssScanner { - peek: number; - peekPeek: number; - length: number = 0; - index: number = -1; - column: number = -1; - line: number = 0; - - _currentMode: CssLexerMode = CssLexerMode.BLOCK; - _currentError: CssScannerError = null; - - constructor(public input: string, private _trackComments: boolean = false) { - this.length = this.input.length; - this.peekPeek = this.peekAt(0); - this.advance(); - } - - getMode(): CssLexerMode { return this._currentMode; } - - setMode(mode: CssLexerMode) { - if (this._currentMode != mode) { - if (_trackWhitespace(this._currentMode)) { - this.consumeWhitespace(); - } - this._currentMode = mode; - } - } - - advance(): void { - if (isNewline(this.peek)) { - this.column = 0; - this.line++; - } else { - this.column++; - } - - this.index++; - this.peek = this.peekPeek; - this.peekPeek = this.peekAt(this.index + 1); - } - - peekAt(index): number { - return index >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, index); - } - - consumeEmptyStatements(): void { - this.consumeWhitespace(); - while (this.peek == $SEMICOLON) { - this.advance(); - this.consumeWhitespace(); - } - } - - consumeWhitespace(): void { - while (isWhitespace(this.peek) || isNewline(this.peek)) { - this.advance(); - if (!this._trackComments && isCommentStart(this.peek, this.peekPeek)) { - this.advance(); // / - this.advance(); // * - while (!isCommentEnd(this.peek, this.peekPeek)) { - if (this.peek == $EOF) { - this.error('Unterminated comment'); - } - this.advance(); - } - this.advance(); // * - this.advance(); // / - } - } - } - - consume(type: CssTokenType, value: string = null): LexedCssResult { - var mode = this._currentMode; - this.setMode(CssLexerMode.ALL); - - var previousIndex = this.index; - var previousLine = this.line; - var previousColumn = this.column; - - var output = this.scan(); - - // just incase the inner scan method returned an error - if (isPresent(output.error)) { - this.setMode(mode); - return output; - } - - var next = output.token; - if (!isPresent(next)) { - next = new CssToken(0, 0, 0, CssTokenType.EOF, "end of file"); - } - - var isMatchingType; - if (type == CssTokenType.IdentifierOrNumber) { - // TODO (matsko): implement array traversal for lookup here - isMatchingType = next.type == CssTokenType.Number || next.type == CssTokenType.Identifier; - } else { - isMatchingType = next.type == type; - } - - // before throwing the error we need to bring back the former - // mode so that the parser can recover... - this.setMode(mode); - - var error = null; - if (!isMatchingType || (isPresent(value) && value != next.strValue)) { - var errorMessage = - CssTokenType[next.type] + " does not match expected " + CssTokenType[type] + " value"; - - if (isPresent(value)) { - errorMessage += ' ("' + next.strValue + '" should match "' + value + '")'; - } - - error = new CssScannerError( - next, generateErrorMessage(this.input, errorMessage, next.strValue, previousIndex, - previousLine, previousColumn)); - } - - return new LexedCssResult(error, next); - } - - - scan(): LexedCssResult { - var trackWS = _trackWhitespace(this._currentMode); - if (this.index == 0 && !trackWS) { // first scan - this.consumeWhitespace(); - } - - var token = this._scan(); - if (token == null) return null; - - var error = this._currentError; - this._currentError = null; - if (!trackWS) { - this.consumeWhitespace(); - } - return new LexedCssResult(error, token); - } - - _scan(): CssToken { - var peek = this.peek; - var peekPeek = this.peekPeek; - if (peek == $EOF) return null; - - if (isCommentStart(peek, peekPeek)) { - // even if comments are not tracked we still lex the - // comment so we can move the pointer forward - var commentToken = this.scanComment(); - if (this._trackComments) { - return commentToken; - } - } - - if (_trackWhitespace(this._currentMode) && (isWhitespace(peek) || isNewline(peek))) { - return this.scanWhitespace(); - } - - peek = this.peek; - peekPeek = this.peekPeek; - if (peek == $EOF) return null; - - if (isStringStart(peek, peekPeek)) { - return this.scanString(); - } - - // something like url(cool) - if (this._currentMode == CssLexerMode.STYLE_VALUE_FUNCTION) { - return this.scanCssValueFunction(); - } - - var isModifier = peek == $PLUS || peek == $MINUS; - var digitA = isModifier ? false : isDigit(peek); - var digitB = isDigit(peekPeek); - if (digitA || (isModifier && (peekPeek == $PERIOD || digitB)) || (peek == $PERIOD && digitB)) { - return this.scanNumber(); - } - - if (peek == $AT) { - return this.scanAtExpression(); - } - - if (isIdentifierStart(peek, peekPeek)) { - return this.scanIdentifier(); - } - - if (isValidCssCharacter(peek, this._currentMode)) { - return this.scanCharacter(); - } - - return this.error(`Unexpected character [${StringWrapper.fromCharCode(peek)}]`); - } - - scanComment() { - if (this.assertCondition(isCommentStart(this.peek, this.peekPeek), "Expected comment start value")) { - return null; - } - - var start = this.index; - var startingColumn = this.column; - var startingLine = this.line; - - this.advance(); // / - this.advance(); // * - - while (!isCommentEnd(this.peek, this.peekPeek)) { - if (this.peek == $EOF) { - this.error('Unterminated comment'); - } - this.advance(); - } - - this.advance(); // * - this.advance(); // / - - var str = this.input.substring(start, this.index); - return new CssToken(start, startingColumn, startingLine, CssTokenType.Comment, str); - } - - scanWhitespace() { - var start = this.index; - var startingColumn = this.column; - var startingLine = this.line; - while (isWhitespace(this.peek) && this.peek != $EOF) { - this.advance(); - } - var str = this.input.substring(start, this.index); - return new CssToken(start, startingColumn, startingLine, CssTokenType.Whitespace, str); - } - - scanString() { - if (this.assertCondition(isStringStart(this.peek, this.peekPeek), "Unexpected non-string starting value")) { - return null; - } - - var target = this.peek; - var start = this.index; - var startingColumn = this.column; - var startingLine = this.line; - var previous = target; - this.advance(); - - while (!isCharMatch(target, previous, this.peek)) { - if (this.peek == $EOF || isNewline(this.peek)) { - this.error('Unterminated quote'); - } - previous = this.peek; - this.advance(); - } - - if (this.assertCondition(this.peek == target, "Unterminated quote")) { - return null; - } - this.advance(); - - var str = this.input.substring(start, this.index); - return new CssToken(start, startingColumn, startingLine, CssTokenType.String, str); - } - - scanNumber() { - var start = this.index; - var startingColumn = this.column; - if (this.peek == $PLUS || this.peek == $MINUS) { - this.advance(); - } - var periodUsed = false; - while (isDigit(this.peek) || this.peek == $PERIOD) { - if (this.peek == $PERIOD) { - if (periodUsed) { - this.error('Unexpected use of a second period value'); - } - periodUsed = true; - } - this.advance(); - } - var strValue = this.input.substring(start, this.index); - return new CssToken(start, startingColumn, this.line, CssTokenType.Number, strValue); - } - - scanIdentifier() { - if (this.assertCondition(isIdentifierStart(this.peek, this.peekPeek), 'Expected identifier starting value')) { - return null; - } - - var start = this.index; - var startingColumn = this.column; - while (isIdentifierPart(this.peek)) { - this.advance(); - } - var strValue = this.input.substring(start, this.index); - return new CssToken(start, startingColumn, this.line, CssTokenType.Identifier, strValue); - } - - scanCssValueFunction() { - var start = this.index; - var startingColumn = this.column; - while (this.peek != $EOF && this.peek != $RPAREN) { - this.advance(); - } - var strValue = this.input.substring(start, this.index); - return new CssToken(start, startingColumn, this.line, CssTokenType.Identifier, strValue); - } - - scanCharacter() { - var start = this.index; - var startingColumn = this.column; - if (this.assertCondition(isValidCssCharacter(this.peek, this._currentMode), charStr(this.peek) + ' is not a valid CSS character')) { - return null; - } - - var c = this.input.substring(start, start + 1); - this.advance(); - - return new CssToken(start, startingColumn, this.line, CssTokenType.Character, c); - } - - scanAtExpression() { - if (this.assertCondition(this.peek == $AT, 'Expected @ value')) { - return null; - } - - var start = this.index; - var startingColumn = this.column; - this.advance(); - if (isIdentifierStart(this.peek, this.peekPeek)) { - var ident = this.scanIdentifier(); - var strValue = '@' + ident.strValue; - return new CssToken(start, startingColumn, this.line, CssTokenType.AtKeyword, strValue); - } else { - return this.scanCharacter(); - } - } - - assertCondition(status: boolean, errorMessage: string): boolean { - if (!status) { - this.error(errorMessage); - return true; - } - return false; - } - - error(message: string, errorTokenValue: string = null, doNotAdvance: boolean = false): CssToken { - var index: number = this.index; - var column: number = this.column; - var line: number = this.line; - errorTokenValue = - isPresent(errorTokenValue) ? errorTokenValue : StringWrapper.fromCharCode(this.peek); - var invalidToken = new CssToken(index, column, line, CssTokenType.Invalid, errorTokenValue); - var errorMessage = - generateErrorMessage(this.input, message, errorTokenValue, index, line, column); - if (!doNotAdvance) { - this.advance(); - } - this._currentError = new CssScannerError(invalidToken, errorMessage); - return invalidToken; - } -} - -function isAtKeyword(current: CssToken, next: CssToken): boolean { - return current.numValue == $AT && next.type == CssTokenType.Identifier; -} - -function isCharMatch(target: number, previous: number, code: number) { - return code == target && previous != $BACKSLASH; -} - -function isDigit(code: number): boolean { - return $0 <= code && code <= $9; -} - -function isCommentStart(code: number, next: number) { - return code == $SLASH && next == $STAR; -} - -function isCommentEnd(code: number, next: number) { - return code == $STAR && next == $SLASH; -} - -function isStringStart(code: number, next: number): boolean { - var target = code; - if (target == $BACKSLASH) { - target = next; - } - return target == $DQ || target == $SQ; -} - -function isIdentifierStart(code: number, next: number): boolean { - var target = code; - if (target == $MINUS) { - target = next; - } - - return ($a <= target && target <= $z) || ($A <= target && target <= $Z) || target == $BACKSLASH || - target == $MINUS || target == $_; -} - -function isIdentifierPart(target: number) { - return ($a <= target && target <= $z) || ($A <= target && target <= $Z) || target == $BACKSLASH || - target == $MINUS || target == $_ || isDigit(target); -} - -function isValidPseudoSelectorCharacter(code: number) { - switch (code) { - case $LPAREN: - case $RPAREN: - return true; - } - return false; -} - -function isValidKeyframeBlockCharacter(code: number) { - switch (code) { - case $PERCENT: - return true; - } - return false; -} - -function isValidAttributeSelectorCharacter(code: number) { - // value^*|$~=something - switch (code) { - case $$: - case $PIPE: - case $CARET: - case $TILDA: - case $STAR: - case $EQ: - return true; - } - return false; -} - -function isValidSelectorCharacter(code: number) { - // selector [ key = value ] - // IDENT C IDENT C IDENT C - // #id, .class, *+~> - // tag:PSEUDO - switch (code) { - case $HASH: - case $PERIOD: - case $TILDA: - case $STAR: - case $PLUS: - case $GT: - case $COLON: - case $PIPE: - case $COMMA: - return true; - } -} - -function isValidStyleBlockCharacter(code: number) { - // key:value; - // key:calc(something ... ) - switch (code) { - case $HASH: - case $SEMICOLON: - case $COLON: - case $PERCENT: - case $SLASH: - case $BACKSLASH: - case $BANG: - case $PERIOD: - case $LPAREN: - case $RPAREN: - return true; - } -} - -function isValidMediaQueryRuleCharacter(code: number) { - // (min-width: 7.5em) and (orientation: landscape) - switch (code) { - case $LPAREN: - case $RPAREN: - case $COLON: - case $PERCENT: - case $PERIOD: - return true; - } -} - -function isValidAtRuleCharacter(code: number) { - // @document url(http://www.w3.org/page?something=on#hash), - switch (code) { - case $LPAREN: - case $RPAREN: - case $COLON: - case $PERCENT: - case $PERIOD: - case $SLASH: - case $BACKSLASH: - case $HASH: - case $EQ: - case $QUESTION: - case $AMPERSAND: - case $STAR: - case $COMMA: - case $MINUS: - case $PLUS: - return true; - } - return false; -} - -function isValidStyleFunctionCharacter(code: number) { - switch (code) { - case $PERIOD: - case $MINUS: - case $PLUS: - case $STAR: - case $SLASH: - case $LPAREN: - case $RPAREN: - case $COMMA: - return true; - } -} - -function isValidBlockCharacter(code: number) { - // @something { } - // IDENT - return code == $AT; -} - -function isValidCssCharacter(code: number, mode: CssLexerMode) { - switch (mode) { - case CssLexerMode.ALL: - case CssLexerMode.ALL_TRACK_WS: - return true; - - case CssLexerMode.SELECTOR: - return isValidSelectorCharacter(code); - - case CssLexerMode.PSEUDO_SELECTOR: - return isValidPseudoSelectorCharacter(code); - - case CssLexerMode.ATTRIBUTE_SELECTOR: - return isValidAttributeSelectorCharacter(code); - - case CssLexerMode.MEDIA_QUERY: - return isValidMediaQueryRuleCharacter(code); - - case CssLexerMode.AT_RULE_QUERY: - return isValidAtRuleCharacter(code); - - case CssLexerMode.KEYFRAME_BLOCK: - return isValidKeyframeBlockCharacter(code); - - case CssLexerMode.STYLE_BLOCK: - case CssLexerMode.STYLE_VALUE: - return isValidStyleBlockCharacter(code); - - case CssLexerMode.STYLE_CALC_FUNCTION: - return isValidStyleFunctionCharacter(code); - - case CssLexerMode.BLOCK: - return isValidBlockCharacter(code); - } - return false; -} - -function charCode(input, index): number { - return index >= input.length ? $EOF : StringWrapper.charCodeAt(input, index); -} - -function charStr(code: number): string { - return StringWrapper.fromCharCode(code); -} - -export function isNewline(code): boolean { - switch (code) { - case $FF: - case $CR: - case $LF: - case $VTAB: - return true; - - default: - return false; - } -} - diff --git a/modules/angular2/src/compiler/css/parser.ts b/modules/angular2/src/compiler/css/parser.ts deleted file mode 100644 index e0fa0477da..0000000000 --- a/modules/angular2/src/compiler/css/parser.ts +++ /dev/null @@ -1,702 +0,0 @@ -import { - ParseSourceSpan, - ParseSourceFile, - ParseLocation, - ParseError -} from "angular2/src/compiler/parse_util"; - -import {NumberWrapper, StringWrapper, isPresent} from "angular2/src/facade/lang"; -import {BaseException} from 'angular2/src/facade/exceptions'; - -import { - CssLexerMode, - CssToken, - CssTokenType, - CssScanner, - CssScannerError, - generateErrorMessage, - $AT, - $EOF, - $RBRACE, - $LBRACE, - $LBRACKET, - $RBRACKET, - $LPAREN, - $RPAREN, - $COMMA, - $COLON, - $SEMICOLON, - isNewline -} from "angular2/src/compiler/css/lexer"; - -export { - CssToken -} from "angular2/src/compiler/css/lexer"; - -export enum BlockType { - Import, - Charset, - Namespace, - Supports, - Keyframes, - MediaQuery, - Selector, - FontFace, - Page, - Document, - Viewport, - Unsupported -} - -const EOF_DELIM = 1; -const RBRACE_DELIM = 2; -const LBRACE_DELIM = 4; -const COMMA_DELIM = 8; -const COLON_DELIM = 16; -const SEMICOLON_DELIM = 32; -const NEWLINE_DELIM = 64; -const RPAREN_DELIM = 128; - -function mergeTokens(tokens: CssToken[], separator: string = ""): CssToken { - var mainToken = tokens[0]; - var str = mainToken.strValue; - for (var i = 1; i < tokens.length; i++) { - str += separator + tokens[i].strValue; - } - - return new CssToken(mainToken.index, mainToken.column, mainToken.line, mainToken.type, str); -} - -function getDelimFromToken(token: CssToken): number { - return getDelimFromCharacter(token.numValue); -} - -function getDelimFromCharacter(code: number): number { - switch (code) { - case $EOF: - return EOF_DELIM; - case $COMMA: - return COMMA_DELIM; - case $COLON: - return COLON_DELIM; - case $SEMICOLON: - return SEMICOLON_DELIM; - case $RBRACE: - return RBRACE_DELIM; - case $LBRACE: - return LBRACE_DELIM; - case $RPAREN: - return RPAREN_DELIM; - default: - return isNewline(code) ? NEWLINE_DELIM : 0; - } -} - -function characterContainsDelimiter(code: number, delimiters: number) { - return (getDelimFromCharacter(code) & delimiters) > 0; -} - -export class CssAST { - visit(visitor: CssASTVisitor, context?: any): void {} -} - -export interface CssASTVisitor { - visitCssValue(ast: CssStyleValueAST, context?: any): void; - visitInlineCssRule(ast: CssInlineRuleAST, context?: any): void; - visitCssKeyframeRule(ast: CssKeyframeRuleAST, context?: any): void; - visitCssKeyframeDefinition(ast: CssKeyframeDefinitionAST, context?: any): void; - visitCssMediaQueryRule(ast: CssMediaQueryRuleAST, context?: any): void; - visitCssSelectorRule(ast: CssSelectorRuleAST, context?: any): void; - visitCssSelector(ast: CssSelectorAST, context?: any): void; - visitCssDefinition(ast: CssDefinitionAST, context?: any): void; - visitCssBlock(ast: CssBlockAST, context?: any): void; - visitCssStyleSheet(ast: CssStyleSheetAST, context?: any): void; - visitUnkownRule(ast: CssUnknownTokenListAST, context?: any): void; -} - -export class ParsedCssResult { - constructor(public errors: CssParseError[], public ast: CssStyleSheetAST) {} -} - -export class CssParser { - private _errors: CssParseError[] = []; - private _file: ParseSourceFile; - - constructor(private _scanner: CssScanner, private _fileName: string) { - this._file = new ParseSourceFile(this._scanner.input, _fileName); - } - - _resolveBlockType(token: CssToken): BlockType { - switch (token.strValue) { - case '@-o-keyframes': - case '@-moz-keyframes': - case '@-webkit-keyframes': - case '@keyframes': - return BlockType.Keyframes; - - case '@charset': - return BlockType.Charset; - - case '@import': - return BlockType.Import; - - case '@namespace': - return BlockType.Namespace; - - case '@page': - return BlockType.Page; - - case '@document': - return BlockType.Document; - - case '@media': - return BlockType.MediaQuery; - - case '@font-face': - return BlockType.FontFace; - - case '@viewport': - return BlockType.Viewport; - - case '@supports': - return BlockType.Supports; - - default: - return BlockType.Unsupported; - } - } - - parse(): ParsedCssResult { - var delimiters: number = EOF_DELIM; - var ast = this._parseStyleSheet(delimiters); - - var errors = this._errors; - this._errors = []; - - return new ParsedCssResult(errors, ast); - } - - _parseStyleSheet(delimiters): CssStyleSheetAST { - var results = []; - this._scanner.consumeEmptyStatements(); - while (this._scanner.peek != $EOF) { - this._scanner.setMode(CssLexerMode.BLOCK); - results.push(this._parseRule(delimiters)); - } - return new CssStyleSheetAST(results); - } - - _parseRule(delimiters: number): CssRuleAST { - if (this._scanner.peek == $AT) { - return this._parseAtRule(delimiters); - } - return this._parseSelectorRule(delimiters); - } - - _parseAtRule(delimiters: number): CssRuleAST { - this._scanner.setMode(CssLexerMode.BLOCK); - - var token = this._scan(); - - this._assertCondition(token.type == CssTokenType.AtKeyword, - `The CSS Rule ${token.strValue} is not a valid [@] rule.`, token); - - var block, type = this._resolveBlockType(token); - switch (type) { - case BlockType.Charset: - case BlockType.Namespace: - case BlockType.Import: - var value = this._parseValue(delimiters); - this._scanner.setMode(CssLexerMode.BLOCK); - this._scanner.consumeEmptyStatements(); - return new CssInlineRuleAST(type, value); - - case BlockType.Viewport: - case BlockType.FontFace: - block = this._parseStyleBlock(delimiters); - return new CssBlockRuleAST(type, block); - - case BlockType.Keyframes: - var tokens = this._collectUntilDelim(delimiters | RBRACE_DELIM | LBRACE_DELIM); - // keyframes only have one identifier name - var name = tokens[0]; - return new CssKeyframeRuleAST(name, this._parseKeyframeBlock(delimiters)); - - case BlockType.MediaQuery: - this._scanner.setMode(CssLexerMode.MEDIA_QUERY); - var tokens = this._collectUntilDelim(delimiters | RBRACE_DELIM | LBRACE_DELIM); - return new CssMediaQueryRuleAST(tokens, this._parseBlock(delimiters)); - - case BlockType.Document: - case BlockType.Supports: - case BlockType.Page: - this._scanner.setMode(CssLexerMode.AT_RULE_QUERY); - var tokens = this._collectUntilDelim(delimiters | RBRACE_DELIM | LBRACE_DELIM); - return new CssBlockDefinitionRuleAST(type, tokens, this._parseBlock(delimiters)); - - // if a custom @rule { ... } is used it should still tokenize the insides - default: - var listOfTokens = [token]; - this._scanner.setMode(CssLexerMode.ALL); - this._error(generateErrorMessage( - this._scanner.input, - `The CSS "at" rule "${token.strValue}" is not allowed to used here`, - token.strValue, token.index, token.line, token.column), - token); - - this._collectUntilDelim(delimiters | LBRACE_DELIM | SEMICOLON_DELIM) - .forEach((token) => { listOfTokens.push(token); }); - if (this._scanner.peek == $LBRACE) { - this._consume(CssTokenType.Character, '{'); - this._collectUntilDelim(delimiters | RBRACE_DELIM | LBRACE_DELIM) - .forEach((token) => { listOfTokens.push(token); }); - this._consume(CssTokenType.Character, '}'); - } - return new CssUnknownTokenListAST(listOfTokens); - } - } - - _parseSelectorRule(delimiters: number): CssSelectorRuleAST { - var selectors = this._parseSelectors(delimiters); - var block = this._parseStyleBlock(delimiters); - this._scanner.setMode(CssLexerMode.BLOCK); - this._scanner.consumeEmptyStatements(); - return new CssSelectorRuleAST(selectors, block); - } - - _parseSelectors(delimiters: number): CssSelectorAST[] { - delimiters |= LBRACE_DELIM; - - var selectors = []; - var isParsingSelectors = true; - while (isParsingSelectors) { - selectors.push(this._parseSelector(delimiters)); - - isParsingSelectors = !characterContainsDelimiter(this._scanner.peek, delimiters); - - if (isParsingSelectors) { - this._consume(CssTokenType.Character, ','); - isParsingSelectors = !characterContainsDelimiter(this._scanner.peek, delimiters); - } - } - - return selectors; - } - - _scan(): CssToken { - var output = this._scanner.scan(); - var token = output.token; - var error = output.error; - if (isPresent(error)) { - this._error(error.rawMessage, token); - } - return token; - } - - _consume(type: CssTokenType, value: string = null): CssToken { - var output = this._scanner.consume(type, value); - var token = output.token; - var error = output.error; - if (isPresent(error)) { - this._error(error.rawMessage, token); - } - return token; - } - - _parseKeyframeBlock(delimiters: number): CssBlockAST { - delimiters |= RBRACE_DELIM; - this._scanner.setMode(CssLexerMode.KEYFRAME_BLOCK); - - this._consume(CssTokenType.Character, '{'); - - var definitions = []; - while (!characterContainsDelimiter(this._scanner.peek, delimiters)) { - definitions.push(this._parseKeyframeDefinition(delimiters)); - } - - this._consume(CssTokenType.Character, '}'); - - return new CssBlockAST(definitions); - } - - _parseKeyframeDefinition(delimiters: number): CssKeyframeDefinitionAST { - var stepTokens = []; - delimiters |= LBRACE_DELIM; - while (!characterContainsDelimiter(this._scanner.peek, delimiters)) { - stepTokens.push(this._parseKeyframeLabel(delimiters | COMMA_DELIM)); - if (this._scanner.peek != $LBRACE) { - this._consume(CssTokenType.Character, ','); - } - } - var styles = this._parseStyleBlock(delimiters | RBRACE_DELIM); - this._scanner.setMode(CssLexerMode.BLOCK); - return new CssKeyframeDefinitionAST(stepTokens, styles); - } - - _parseKeyframeLabel(delimiters: number): CssToken { - this._scanner.setMode(CssLexerMode.KEYFRAME_BLOCK); - return mergeTokens(this._collectUntilDelim(delimiters)); - } - - _parseSelector(delimiters: number): CssSelectorAST { - delimiters |= COMMA_DELIM | LBRACE_DELIM; - this._scanner.setMode(CssLexerMode.SELECTOR); - - var selectorCssTokens = []; - var isComplex = false; - var wsCssToken; - - var previousToken; - var parenCount = 0; - while (!characterContainsDelimiter(this._scanner.peek, delimiters)) { - var code = this._scanner.peek; - switch (code) { - case $LPAREN: - parenCount++; - break; - - case $RPAREN: - parenCount--; - break; - - case $COLON: - this._scanner.setMode(CssLexerMode.PSEUDO_SELECTOR); - previousToken = this._consume(CssTokenType.Character, ':'); - selectorCssTokens.push(previousToken); - continue; - break; - - case $LBRACKET: - // if we are already inside an attribute selector then we can't - // jump into the mode again. Therefore this error will get picked - // up when the scan method is called below. - if (this._scanner.getMode() != CssLexerMode.ATTRIBUTE_SELECTOR) { - selectorCssTokens.push(this._consume(CssTokenType.Character, '[')); - this._scanner.setMode(CssLexerMode.ATTRIBUTE_SELECTOR); - continue; - } - break; - - case $RBRACKET: - selectorCssTokens.push(this._consume(CssTokenType.Character, ']')); - this._scanner.setMode(CssLexerMode.SELECTOR); - continue; - break; - } - - var token = this._scan(); - - // special case for the ":not(" selector since it - // contains an inner selector that needs to be parsed - // in isolation - if (this._scanner.getMode() == CssLexerMode.PSEUDO_SELECTOR && isPresent(previousToken) && - previousToken.numValue == $COLON && token.strValue == "not" && - this._scanner.peek == $LPAREN) { - - selectorCssTokens.push(token); - selectorCssTokens.push(this._consume(CssTokenType.Character, '(')); - - // the inner selector inside of :not(...) can only be one - // CSS selector (no commas allowed) therefore we parse only - // one selector by calling the method below - this._parseSelector(delimiters | RPAREN_DELIM).tokens.forEach( - (innerSelectorToken) => { selectorCssTokens.push(innerSelectorToken); }); - - selectorCssTokens.push(this._consume(CssTokenType.Character, ')')); - - continue; - } - - previousToken = token; - - if (token.type == CssTokenType.Whitespace) { - wsCssToken = token; - } else { - if (isPresent(wsCssToken)) { - selectorCssTokens.push(wsCssToken); - wsCssToken = null; - isComplex = true; - } - selectorCssTokens.push(token); - } - } - - if (this._scanner.getMode() == CssLexerMode.ATTRIBUTE_SELECTOR) { - this._error("Unbalanced CSS attribute selector at column " + previousToken.line + ":" + - previousToken.column, - previousToken); - } else if (parenCount > 0) { - this._error("Unbalanced pseudo selector function value at column " + previousToken.line + - ":" + previousToken.column, - previousToken); - } - - return new CssSelectorAST(selectorCssTokens, isComplex); - } - - _parseValue(delimiters: number): CssStyleValueAST { - delimiters |= RBRACE_DELIM | SEMICOLON_DELIM | NEWLINE_DELIM; - - this._scanner.setMode(CssLexerMode.STYLE_VALUE); - - var tokens = []; - var previous: CssToken; - while (!characterContainsDelimiter(this._scanner.peek, delimiters)) { - var token; - if (isPresent(previous) && previous.type == CssTokenType.Identifier && this._scanner.peek == $LPAREN) { - tokens.push(this._consume(CssTokenType.Character, '(')); - - this._scanner.setMode(CssLexerMode.STYLE_VALUE_FUNCTION); - tokens.push(this._scan()); - this._scanner.setMode(CssLexerMode.STYLE_VALUE); - - token = this._consume(CssTokenType.Character, ')'); - tokens.push(token); - } else { - token = this._scan(); - if (token.type != CssTokenType.Whitespace) { - tokens.push(token); - } - } - - previous = token; - } - - this._scanner.consumeWhitespace(); - - var code = this._scanner.peek; - if (code == $SEMICOLON) { - this._consume(CssTokenType.Character, ';'); - } else if (code != $RBRACE) { - this._error( - generateErrorMessage(this._scanner.input, - `The CSS key/value definition did not end with a semicolon`, - previous.strValue, previous.index, previous.line, previous.column), - previous); - } - - return new CssStyleValueAST(tokens); - } - - _collectUntilDelim(delimiters: number, assertType: CssTokenType = null): CssToken[] { - var tokens = []; - while (!characterContainsDelimiter(this._scanner.peek, delimiters)) { - var val = isPresent(assertType) ? this._consume(assertType) : this._scan(); - tokens.push(val); - } - return tokens; - } - - _parseBlock(delimiters: number): CssBlockAST { - delimiters |= RBRACE_DELIM; - - this._scanner.setMode(CssLexerMode.BLOCK); - - this._consume(CssTokenType.Character, '{'); - this._scanner.consumeEmptyStatements(); - - var results = []; - while (!characterContainsDelimiter(this._scanner.peek, delimiters)) { - results.push(this._parseRule(delimiters)); - } - - this._consume(CssTokenType.Character, '}'); - - this._scanner.setMode(CssLexerMode.BLOCK); - this._scanner.consumeEmptyStatements(); - - return new CssBlockAST(results); - } - - _parseStyleBlock(delimiters: number): CssBlockAST { - delimiters |= RBRACE_DELIM | LBRACE_DELIM; - - this._scanner.setMode(CssLexerMode.STYLE_BLOCK); - - this._consume(CssTokenType.Character, '{'); - this._scanner.consumeEmptyStatements(); - - var definitions = []; - while (!characterContainsDelimiter(this._scanner.peek, delimiters)) { - definitions.push(this._parseDefinition(delimiters)); - this._scanner.consumeEmptyStatements(); - } - - this._consume(CssTokenType.Character, '}'); - - this._scanner.setMode(CssLexerMode.STYLE_BLOCK); - this._scanner.consumeEmptyStatements(); - - return new CssBlockAST(definitions); - } - - _parseDefinition(delimiters: number): CssDefinitionAST { - this._scanner.setMode(CssLexerMode.STYLE_BLOCK); - - var prop = this._consume(CssTokenType.Identifier); - var parseValue, value = 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 $COLON: - this._consume(CssTokenType.Character, ':'); - parseValue = true; - break; - - case $SEMICOLON: - case $RBRACE: - case $EOF: - parseValue = false; - break; - - default: - var propStr = [prop.strValue]; - if (this._scanner.peek != $COLON) { - // this will throw the error - var nextValue = this._consume(CssTokenType.Character, ':'); - propStr.push(nextValue.strValue); - - var remainingTokens = this._collectUntilDelim(delimiters | COLON_DELIM | SEMICOLON_DELIM, - CssTokenType.Identifier); - if (remainingTokens.length > 0) { - remainingTokens.forEach((token) => { propStr.push(token.strValue); }); - } - - 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 == $COLON) { - this._consume(CssTokenType.Character, ':'); - parseValue = true; - } else { - parseValue = false; - } - break; - } - - if (parseValue) { - value = this._parseValue(delimiters); - } else { - this._error(generateErrorMessage(this._scanner.input, - `The CSS property was not paired with a style value`, - prop.strValue, prop.index, prop.line, prop.column), - prop); - } - - return new CssDefinitionAST(prop, value); - } - - _assertCondition(status: boolean, errorMessage: string, problemToken: CssToken): boolean { - if (!status) { - this._error(errorMessage, problemToken); - return true; - } - return false; - } - - _error(message: string, problemToken: CssToken) { - var length = problemToken.strValue.length; - var error = - new CssParseError(this._file, 0, problemToken.line, problemToken.column, length, message); - this._errors.push(error); - } -} - -export class CssStyleValueAST extends CssAST { - constructor(public tokens: CssToken[]) { super(); } - visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssValue(this); } -} - -export class CssRuleAST extends CssAST {} - -export class CssBlockRuleAST extends CssRuleAST { - constructor(public type: BlockType, public block: CssBlockAST, public name: CssToken = null) { - super(); - } - visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssBlock(this.block, context); } -} - -export class CssKeyframeRuleAST extends CssBlockRuleAST { - constructor(name: CssToken, block: CssBlockAST) { super(BlockType.Keyframes, block, name); } - visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssKeyframeRule(this, context); } -} - -export class CssKeyframeDefinitionAST extends CssBlockRuleAST { - constructor(public steps: CssToken[], block: CssBlockAST) { super(BlockType.Keyframes, block, mergeTokens(steps, ",")); } - visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssKeyframeDefinition(this, context); } -} - -export class CssBlockDefinitionRuleAST extends CssBlockRuleAST { - public strValue: string; - constructor(type: BlockType, public query: CssToken[], block: CssBlockAST) { - super(type, block); - this.strValue = query.map(token => token.strValue).join(""); - var firstCssToken: CssToken = query[0]; - this.name = new CssToken(firstCssToken.index, firstCssToken.column, firstCssToken.line, - CssTokenType.Identifier, this.strValue); - } - visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssBlock(this.block, context); } -} - -export class CssMediaQueryRuleAST extends CssBlockDefinitionRuleAST { - constructor(query: CssToken[], block: CssBlockAST) { super(BlockType.MediaQuery, query, block); } - visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssMediaQueryRule(this, context); } -} - -export class CssInlineRuleAST extends CssRuleAST { - constructor(public type: BlockType, public value: CssStyleValueAST) { super(); } - visit(visitor: CssASTVisitor, context?: any) { visitor.visitInlineCssRule(this, context); } -} - -export class CssSelectorRuleAST extends CssBlockRuleAST { - public strValue: string; - - constructor(public selectors: CssSelectorAST[], block: CssBlockAST) { - super(BlockType.Selector, block); - this.strValue = selectors.map(selector => selector.strValue).join(","); - } - - visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssSelectorRule(this, context); } -} - -export class CssDefinitionAST extends CssAST { - constructor(public property: CssToken, public value: CssStyleValueAST) { super(); } - visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssDefinition(this, context); } -} - -export class CssSelectorAST extends CssAST { - public strValue; - constructor(public tokens: CssToken[], public isComplex: boolean = false) { - super(); - this.strValue = tokens.map(token => token.strValue).join(""); - } - visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssSelector(this, context); } -} - -export class CssBlockAST extends CssAST { - constructor(public entries: CssAST[]) { super(); } - visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssBlock(this, context); } -} - -export class CssStyleSheetAST extends CssAST { - constructor(public rules: CssAST[]) { super(); } - visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssStyleSheet(this, context); } -} - -export class CssParseError extends ParseError { - constructor(file: ParseSourceFile, offset: number, line: number, col: number, length: number, - errMsg: string) { - var start = new ParseLocation(file, offset, line, col); - var end = new ParseLocation(file, offset, line, col + length); - var span = new ParseSourceSpan(start, end); - super(span, "CSS Parse Error: " + errMsg); - } -} - -export class CssUnknownTokenListAST extends CssAST { - constructor(public tokens: CssToken[]) { super(); } - visit(visitor: CssASTVisitor, context?: any) { visitor.visitUnkownRule(this, context); } -} diff --git a/modules/angular2/src/core/change_detection/parser/lexer.ts b/modules/angular2/src/core/change_detection/parser/lexer.ts index 9f5207e35e..dcfdb3f0f6 100644 --- a/modules/angular2/src/core/change_detection/parser/lexer.ts +++ b/modules/angular2/src/core/change_detection/parser/lexer.ts @@ -153,6 +153,7 @@ export const $BAR = 124; export const $RBRACE = 125; const $NBSP = 160; + export class ScannerError extends BaseException { constructor(public message) { super(); } @@ -378,7 +379,7 @@ class _Scanner { } } -export function isWhitespace(code: number): boolean { +function isWhitespace(code: number): boolean { return (code >= $TAB && code <= $SPACE) || (code == $NBSP); } diff --git a/modules/angular2/test/compiler/css/lexer_spec.ts b/modules/angular2/test/compiler/css/lexer_spec.ts deleted file mode 100644 index 4341ce960a..0000000000 --- a/modules/angular2/test/compiler/css/lexer_spec.ts +++ /dev/null @@ -1,386 +0,0 @@ -import { - ddescribe, - describe, - it, - iit, - xit, - expect, - beforeEach, - afterEach -} from 'angular2/testing_internal'; - -import {isPresent} from "angular2/src/facade/lang"; - -import {CssToken, CssLexer, CssLexerMode, CssTokenType} from 'angular2/src/compiler/css/lexer'; - -export function main() { - function tokenize(code, trackComments: boolean = false, - mode: CssLexerMode = CssLexerMode.ALL): CssToken[] { - var scanner = new CssLexer().scan(code, trackComments); - scanner.setMode(mode); - - var tokens = []; - var output = scanner.scan(); - while (output != null) { - if (isPresent(output.error)) { - throw output.error; - } - tokens.push(output.token); - output = scanner.scan(); - } - - return tokens; - } - - describe('CssLexer', () => { - it('should lex newline characters as whitespace when whitespace mode is on', () => { - var newlines = ["\n", "\r\n", "\r", "\f"]; - newlines.forEach((line) => { - var token = tokenize(line, false, CssLexerMode.ALL_TRACK_WS)[0]; - expect(token.type).toEqual(CssTokenType.Whitespace); - }); - }); - - it('should combined newline characters as one newline token when whitespace mode is on', () => { - var newlines = ["\n", "\r\n", "\r", "\f"].join(""); - var tokens = tokenize(newlines, false, CssLexerMode.ALL_TRACK_WS); - expect(tokens.length).toEqual(1); - expect(tokens[0].type).toEqual(CssTokenType.Whitespace); - }); - - it('should not consider whitespace or newline values at all when whitespace mode is off', - () => { - var newlines = ["\n", "\r\n", "\r", "\f"].join(""); - var tokens = tokenize(newlines); - expect(tokens.length).toEqual(0); - }); - - it('should lex simple selectors and their inner properties', () => { - var cssCode = "\n" + " .selector { my-prop: my-value; }\n"; - var tokens = tokenize(cssCode); - - expect(tokens[0].type).toEqual(CssTokenType.Character); - expect(tokens[0].strValue).toEqual('.'); - - expect(tokens[1].type).toEqual(CssTokenType.Identifier); - expect(tokens[1].strValue).toEqual('selector'); - - expect(tokens[2].type).toEqual(CssTokenType.Character); - expect(tokens[2].strValue).toEqual('{'); - - expect(tokens[3].type).toEqual(CssTokenType.Identifier); - expect(tokens[3].strValue).toEqual('my-prop'); - - expect(tokens[4].type).toEqual(CssTokenType.Character); - expect(tokens[4].strValue).toEqual(':'); - - expect(tokens[5].type).toEqual(CssTokenType.Identifier); - expect(tokens[5].strValue).toEqual('my-value'); - - expect(tokens[6].type).toEqual(CssTokenType.Character); - expect(tokens[6].strValue).toEqual(';'); - - expect(tokens[7].type).toEqual(CssTokenType.Character); - expect(tokens[7].strValue).toEqual('}'); - }); - - it('should capture the column and line values for each token', () => { - var cssCode = "#id {\n" + " prop:value;\n" + "}"; - - var tokens = tokenize(cssCode); - - // # - expect(tokens[0].type).toEqual(CssTokenType.Character); - expect(tokens[0].column).toEqual(0); - expect(tokens[0].line).toEqual(0); - - // id - expect(tokens[1].type).toEqual(CssTokenType.Identifier); - expect(tokens[1].column).toEqual(1); - expect(tokens[1].line).toEqual(0); - - // { - expect(tokens[2].type).toEqual(CssTokenType.Character); - expect(tokens[2].column).toEqual(4); - expect(tokens[2].line).toEqual(0); - - // prop - expect(tokens[3].type).toEqual(CssTokenType.Identifier); - expect(tokens[3].column).toEqual(2); - expect(tokens[3].line).toEqual(1); - - // : - expect(tokens[4].type).toEqual(CssTokenType.Character); - expect(tokens[4].column).toEqual(6); - expect(tokens[4].line).toEqual(1); - - // value - expect(tokens[5].type).toEqual(CssTokenType.Identifier); - expect(tokens[5].column).toEqual(7); - expect(tokens[5].line).toEqual(1); - - // ; - expect(tokens[6].type).toEqual(CssTokenType.Character); - expect(tokens[6].column).toEqual(12); - expect(tokens[6].line).toEqual(1); - - // } - expect(tokens[7].type).toEqual(CssTokenType.Character); - expect(tokens[7].column).toEqual(0); - expect(tokens[7].line).toEqual(2); - }); - - it('should lex quoted strings and escape accordingly', () => { - var cssCode = "prop: 'some { value } \\' that is quoted'"; - var tokens = tokenize(cssCode); - - expect(tokens[0].type).toEqual(CssTokenType.Identifier); - expect(tokens[1].type).toEqual(CssTokenType.Character); - expect(tokens[2].type).toEqual(CssTokenType.String); - expect(tokens[2].strValue).toEqual("'some { value } \\' that is quoted'"); - }); - - it('should treat attribute operators as regular characters', () => { - tokenize('^|~+*').forEach((token) => { expect(token.type).toEqual(CssTokenType.Character); }); - }); - - it('should lex numbers properly and set them as numbers', () => { - var cssCode = "0 1 -2 3.0 -4.001"; - var tokens = tokenize(cssCode); - - expect(tokens[0].type).toEqual(CssTokenType.Number); - expect(tokens[0].strValue).toEqual("0"); - - expect(tokens[1].type).toEqual(CssTokenType.Number); - expect(tokens[1].strValue).toEqual("1"); - - expect(tokens[2].type).toEqual(CssTokenType.Number); - expect(tokens[2].strValue).toEqual("-2"); - - expect(tokens[3].type).toEqual(CssTokenType.Number); - expect(tokens[3].strValue).toEqual("3.0"); - - expect(tokens[4].type).toEqual(CssTokenType.Number); - expect(tokens[4].strValue).toEqual("-4.001"); - }); - - it('should lex @keywords', () => { - var cssCode = "@import()@something"; - var tokens = tokenize(cssCode); - - expect(tokens[0].type).toEqual(CssTokenType.AtKeyword); - expect(tokens[0].strValue).toEqual('@import'); - - expect(tokens[1].type).toEqual(CssTokenType.Character); - expect(tokens[1].strValue).toEqual('('); - - expect(tokens[2].type).toEqual(CssTokenType.Character); - expect(tokens[2].strValue).toEqual(')'); - - expect(tokens[3].type).toEqual(CssTokenType.AtKeyword); - expect(tokens[3].strValue).toEqual('@something'); - }); - - it('should still lex a number even if it has a dimension suffix', () => { - var cssCode = "40% is 40 percent"; - var tokens = tokenize(cssCode); - - expect(tokens[0].type).toEqual(CssTokenType.Number); - expect(tokens[0].strValue).toEqual('40'); - - expect(tokens[1].type).toEqual(CssTokenType.Character); - expect(tokens[1].strValue).toEqual('%'); - - expect(tokens[2].type).toEqual(CssTokenType.Identifier); - expect(tokens[2].strValue).toEqual('is'); - - expect(tokens[3].type).toEqual(CssTokenType.Number); - expect(tokens[3].strValue).toEqual('40'); - }); - - it('should allow escaped character and unicode character-strings in CSS selectors', () => { - var cssCode = "\\123456 .some\\thing \{\}"; - var tokens = tokenize(cssCode); - - expect(tokens[0].type).toEqual(CssTokenType.Identifier); - expect(tokens[0].strValue).toEqual('\\123456'); - - expect(tokens[1].type).toEqual(CssTokenType.Character); - expect(tokens[2].type).toEqual(CssTokenType.Identifier); - expect(tokens[2].strValue).toEqual('some\\thing'); - }); - - it('should distinguish identifiers and numbers from special characters', () => { - var cssCode = "one*two=-4+three-4-equals_value$"; - var tokens = tokenize(cssCode); - - expect(tokens[0].type).toEqual(CssTokenType.Identifier); - expect(tokens[0].strValue).toEqual("one"); - - expect(tokens[1].type).toEqual(CssTokenType.Character); - expect(tokens[1].strValue).toEqual("*"); - - expect(tokens[2].type).toEqual(CssTokenType.Identifier); - expect(tokens[2].strValue).toEqual("two"); - - expect(tokens[3].type).toEqual(CssTokenType.Character); - expect(tokens[3].strValue).toEqual("="); - - expect(tokens[4].type).toEqual(CssTokenType.Number); - expect(tokens[4].strValue).toEqual("-4"); - - expect(tokens[5].type).toEqual(CssTokenType.Character); - expect(tokens[5].strValue).toEqual("+"); - - expect(tokens[6].type).toEqual(CssTokenType.Identifier); - expect(tokens[6].strValue).toEqual("three-4-equals_value"); - - expect(tokens[7].type).toEqual(CssTokenType.Character); - expect(tokens[7].strValue).toEqual("$"); - }); - - it('should filter out comments and whitespace by default', () => { - var cssCode = ".selector /* comment */ { /* value */ }"; - var tokens = tokenize(cssCode); - - expect(tokens[0].strValue).toEqual("."); - expect(tokens[1].strValue).toEqual("selector"); - expect(tokens[2].strValue).toEqual("{"); - expect(tokens[3].strValue).toEqual("}"); - }); - - it('should track comments when the flag is set to true', () => { - var cssCode = ".selector /* comment */ { /* value */ }"; - var trackComments = true; - var tokens = tokenize(cssCode, trackComments, CssLexerMode.ALL_TRACK_WS); - - expect(tokens[0].strValue).toEqual("."); - expect(tokens[1].strValue).toEqual("selector"); - expect(tokens[2].strValue).toEqual(" "); - - expect(tokens[3].type).toEqual(CssTokenType.Comment); - expect(tokens[3].strValue).toEqual("/* comment */"); - - expect(tokens[4].strValue).toEqual(" "); - expect(tokens[5].strValue).toEqual("{"); - expect(tokens[6].strValue).toEqual(" "); - - expect(tokens[7].type).toEqual(CssTokenType.Comment); - expect(tokens[7].strValue).toEqual("/* value */"); - }); - - describe('Selector Mode', () => { - it('should throw an error if a selector is being parsed while in the wrong mode', () => { - var cssCode = ".class > tag"; - - var capturedMessage; - try { - tokenize(cssCode, false, CssLexerMode.STYLE_BLOCK); - } catch (e) { - capturedMessage = e.message; - } - - expect(capturedMessage).toMatch(/Unexpected character \[>\] at column 0:7 in expression/g); - - capturedMessage = null; - var capturedMessage; - try { - tokenize(cssCode, false, CssLexerMode.SELECTOR); - } catch (e) { - capturedMessage = e.message; - } - - expect(capturedMessage).toEqual(null); - }); - }); - - describe('Attribute Mode', () => { - it('should consider attribute selectors as valid input and throw when an invalid modifier is used', - () => { - function tokenizeAttr(modifier) { - var cssCode = "value" + modifier + "='something'"; - return tokenize(cssCode, false, CssLexerMode.ATTRIBUTE_SELECTOR); - } - - expect(tokenizeAttr("*").length).toEqual(4); - expect(tokenizeAttr("|").length).toEqual(4); - expect(tokenizeAttr("^").length).toEqual(4); - expect(tokenizeAttr("$").length).toEqual(4); - expect(tokenizeAttr("~").length).toEqual(4); - expect(tokenizeAttr("").length).toEqual(3); - - expect(() => { tokenizeAttr("+"); }).toThrow(); - }); - }); - - describe('Media Query Mode', () => { - it('should validate media queries with a reduced subset of valid characters', () => { - function tokenizeQuery(code) { return tokenize(code, false, CssLexerMode.MEDIA_QUERY); } - - // the reason why the numbers are so high is because MediaQueries keep - // track of the whitespace values - expect(tokenizeQuery("(prop: value)").length).toEqual(5); - expect(tokenizeQuery("(prop: value) and (prop2: value2)").length).toEqual(11); - expect(tokenizeQuery("tv and (prop: value)").length).toEqual(7); - expect(tokenizeQuery("print and ((prop: value) or (prop2: value2))").length).toEqual(15); - expect(tokenizeQuery("(content: 'something $ crazy inside &')").length).toEqual(5); - - expect(() => { tokenizeQuery("(max-height: 10 + 20)"); }).toThrow(); - - expect(() => { tokenizeQuery("(max-height: fifty < 100)"); }).toThrow(); - }); - }); - - describe('Pseudo Selector Mode', () => { - it('should validate pseudo selector identifiers with a reduced subset of valid characters', - () => { - function tokenizePseudo(code) { - return tokenize(code, false, CssLexerMode.PSEUDO_SELECTOR); - } - - expect(tokenizePseudo("lang(en-us)").length).toEqual(4); - expect(tokenizePseudo("hover").length).toEqual(1); - expect(tokenizePseudo("focus").length).toEqual(1); - - expect(() => { tokenizePseudo("lang(something:broken)"); }).toThrow(); - - expect(() => { tokenizePseudo("not(.selector)"); }).toThrow(); - }); - }); - - describe('Pseudo Selector Mode', () => { - it('should validate pseudo selector identifiers with a reduced subset of valid characters', - () => { - function tokenizePseudo(code) { - return tokenize(code, false, CssLexerMode.PSEUDO_SELECTOR); - } - - expect(tokenizePseudo("lang(en-us)").length).toEqual(4); - expect(tokenizePseudo("hover").length).toEqual(1); - expect(tokenizePseudo("focus").length).toEqual(1); - - expect(() => { tokenizePseudo("lang(something:broken)"); }).toThrow(); - - expect(() => { tokenizePseudo("not(.selector)"); }).toThrow(); - }); - }); - - describe('Style Block Mode', () => { - it('should style blocks with a reduced subset of valid characters', () => { - function tokenizeStyles(code) { return tokenize(code, false, CssLexerMode.STYLE_BLOCK); } - - expect(tokenizeStyles(` - key: value; - prop: 100; - style: value3!important; - `).length) - .toEqual(14); - - expect(() => tokenizeStyles(` key$: value; `)).toThrow(); - expect(() => tokenizeStyles(` key: value$; `)).toThrow(); - expect(() => tokenizeStyles(` key: value + 10; `)).toThrow(); - expect(() => tokenizeStyles(` key: &value; `)).toThrow(); - }); - }); - }); -} diff --git a/modules/angular2/test/compiler/css/parser_spec.ts b/modules/angular2/test/compiler/css/parser_spec.ts deleted file mode 100644 index ab838b7dc7..0000000000 --- a/modules/angular2/test/compiler/css/parser_spec.ts +++ /dev/null @@ -1,635 +0,0 @@ -import { - ddescribe, - describe, - it, - iit, - xit, - expect, - beforeEach, - afterEach -} from 'angular2/testing_internal'; - -import {BaseException} from 'angular2/src/facade/exceptions'; - -import { - ParsedCssResult, - CssParser, - BlockType, - CssSelectorRuleAST, - CssKeyframeRuleAST, - CssKeyframeDefinitionAST, - CssBlockDefinitionRuleAST, - CssMediaQueryRuleAST, - CssBlockRuleAST, - CssInlineRuleAST, - CssStyleValueAST, - CssSelectorAST, - CssDefinitionAST, - CssStyleSheetAST, - CssRuleAST, - CssBlockAST, - CssParseError -} from 'angular2/src/compiler/css/parser'; - -import {CssLexer} from 'angular2/src/compiler/css/lexer'; - -export function assertTokens(tokens, valuesArr) { - for (var i = 0; i < tokens.length; i++) { - expect(tokens[i].strValue == valuesArr[i]); - } -} - -export function main() { - describe('CssParser', () => { - function parse(css): 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): CssStyleSheetAST { - var output = parse(css); - var errors = output.errors; - if (errors.length > 0) { - throw new BaseException(errors.map((error: CssParseError) => error.msg).join(', ')); - } - return output.ast; - } - - it('should parse CSS into a stylesheet AST', () => { - var styles = ` - .selector { - prop: value123; - } - `; - - var ast = makeAST(styles); - expect(ast.rules.length).toEqual(1); - - var rule = ast.rules[0]; - var selector = rule.selectors[0]; - expect(selector.strValue).toEqual('.selector'); - - var block: CssBlockAST = rule.block; - expect(block.entries.length).toEqual(1); - - var definition = block.entries[0]; - expect(definition.property.strValue).toEqual('prop'); - - var value = definition.value; - expect(value.tokens[0].strValue).toEqual('value123'); - }); - - it('should parse mutliple CSS selectors sharing the same set of styles', () => { - var styles = ` - .class, #id, tag, [attr], key + value, * value, :-moz-any-link { - prop: value123; - } - `; - - var ast = makeAST(styles); - expect(ast.rules.length).toEqual(1); - - var rule = ast.rules[0]; - expect(rule.selectors.length).toBe(7); - - assertTokens(rule.selectors[0].tokens, [".", "class"]); - assertTokens(rule.selectors[1].tokens, ["#", "id"]); - assertTokens(rule.selectors[2].tokens, ["tag"]); - assertTokens(rule.selectors[3].tokens, ["[", "attr", "]"]); - assertTokens(rule.selectors[4].tokens, ["key", "+", "value"]); - assertTokens(rule.selectors[5].tokens, ["*", "value"]); - assertTokens(rule.selectors[6].tokens, [":", "-moz-any-link"]); - - var style1 = rule.block.entries[0]; - expect(style1.property.strValue).toBe("prop"); - assertTokens(style1.value.tokens, ["value123"]); - }); - - it('should parse keyframe rules', () => { - var styles = ` - @keyframes rotateMe { - from { - transform: rotate(-360deg); - } - 50% { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } - } - `; - - var ast = makeAST(styles); - expect(ast.rules.length).toEqual(1); - - var rule = ast.rules[0]; - expect(rule.name.strValue).toEqual('rotateMe'); - - var block = rule.block; - var fromRule = block.entries[0]; - - expect(fromRule.name.strValue).toEqual('from'); - var fromStyle = (fromRule.block).entries[0]; - expect(fromStyle.property.strValue).toEqual('transform'); - assertTokens(fromStyle.value.tokens, ['rotate', '(', '-360', 'deg', ')']); - - var midRule = block.entries[1]; - - expect(midRule.name.strValue).toEqual('50%'); - var midStyle = (midRule.block).entries[0]; - expect(midStyle.property.strValue).toEqual('transform'); - assertTokens(midStyle.value.tokens, ['rotate', '(', '0', 'deg', ')']); - - var toRule = block.entries[2]; - - expect(toRule.name.strValue).toEqual('to'); - var toStyle = (toRule.block).entries[0]; - expect(toStyle.property.strValue).toEqual('transform'); - assertTokens(toStyle.value.tokens, ['rotate', '(', '360', 'deg', ')']); - }); - - it('should parse media queries into a stylesheet AST', () => { - var styles = ` - @media all and (max-width:100px) { - .selector { - prop: value123; - } - } - `; - - var ast = makeAST(styles); - expect(ast.rules.length).toEqual(1); - - var rule = ast.rules[0]; - assertTokens(rule.query, ['all', 'and', '(', 'max-width', ':', '100px', ')']); - - var block = rule.block; - expect(block.entries.length).toEqual(1); - - var rule2 = block.entries[0]; - expect(rule2.selectors[0].strValue).toEqual('.selector'); - - var block2 = rule2.block; - expect(block2.entries.length).toEqual(1); - }); - - it('should parse inline CSS values', () => { - var styles = ` - @import url('remote.css'); - @charset "UTF-8"; - @namespace ng url(http://angular.io/namespace/ng); - `; - - var ast = makeAST(styles); - - var importRule = ast.rules[0]; - expect(importRule.type).toEqual(BlockType.Import); - assertTokens(importRule.value.tokens, ["url", "(", "remote", ".", "css", ")"]); - - var charsetRule = ast.rules[1]; - expect(charsetRule.type).toEqual(BlockType.Charset); - assertTokens(importRule.value.tokens, ["UTF-8"]); - - var namespaceRule = ast.rules[2]; - expect(namespaceRule.type).toEqual(BlockType.Namespace); - assertTokens(namespaceRule.value.tokens, - ["ng", "url", "(", "http://angular.io/namespace/ng", ")"]); - }); - - it('should parse CSS values that contain functions and leave the inner function data untokenized', () => { - var styles = ` - .class { - background: url(matias.css); - animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); - height: calc(100% - 50px); - } - `; - - var ast = makeAST(styles); - expect(ast.rules.length).toEqual(1); - - var defs = (ast.rules[0]).block.entries; - expect(defs.length).toEqual(3); - - assertTokens((defs[0]).value, ['url','(','matias.css',')']); - assertTokens((defs[1]).value, ['cubic-bezier','(','0.755, 0.050, 0.855, 0.060',')']); - assertTokens((defs[2]).value, ['calc','(','100% - 50px',')']); - }); - - it('should parse un-named block-level CSS values', () => { - var styles = ` - @font-face { - font-family: "Matias"; - font-weight: bold; - src: url(font-face.ttf); - } - @viewport { - max-width: 100px; - min-height: 1000px; - } - `; - - var ast = makeAST(styles); - - var fontFaceRule = ast.rules[0]; - expect(fontFaceRule.type).toEqual(BlockType.FontFace); - expect(fontFaceRule.block.entries.length).toEqual(3); - - var viewportRule = ast.rules[1]; - expect(viewportRule.type).toEqual(BlockType.Viewport); - expect(viewportRule.block.entries.length).toEqual(2); - }); - - it('should parse multiple levels of semicolons', () => { - var styles = ` - ;;; - @import url('something something') - ;;;;;;;; - ;;;;;;;; - ;@font-face { - ;src : url(font-face.ttf);;;;;;;; - ;;;-webkit-animation:my-animation - };;; - @media all and (max-width:100px) - {; - .selector {prop: value123;}; - ;.selector2{prop:1}} - `; - - var ast = makeAST(styles); - - var importRule = ast.rules[0]; - expect(importRule.type).toEqual(BlockType.Import); - assertTokens(importRule.value.tokens, ["url", "(", "something something", ")"]); - - var fontFaceRule = ast.rules[1]; - expect(fontFaceRule.type).toEqual(BlockType.FontFace); - expect(fontFaceRule.block.entries.length).toEqual(2); - - var mediaQueryRule = ast.rules[2]; - assertTokens(mediaQueryRule.query, ['all', 'and', '(', 'max-width', ':', '100px', ')']); - expect(mediaQueryRule.block.entries.length).toEqual(2); - }); - - it('should throw an error if an unknown @value block rule is parsed', () => { - var styles = ` - @matias { hello: there; } - `; - - expect(() => { - makeAST(styles); - }).toThrowError(/^CSS Parse Error: The CSS "at" rule "@matias" is not allowed to used here/); - }); - - it('should parse empty rules', () => { - var styles = ` - .empty-rule { } - .somewhat-empty-rule { /* property: value; */ } - .non-empty-rule { property: value; } - `; - - var ast = makeAST(styles); - - var rules = ast.rules; - expect((rules[0]).block.entries.length).toEqual(0); - expect((rules[1]).block.entries.length).toEqual(0); - expect((rules[2]).block.entries.length).toEqual(1); - }); - - it('should parse the @document rule', () => { - var styles = ` - @document url(http://www.w3.org/), - url-prefix(http://www.w3.org/Style/), - domain(mozilla.org), - regexp("https:.*") - { - /* CSS rules here apply to: - - The page "http://www.w3.org/". - - Any page whose URL begins with "http://www.w3.org/Style/" - - Any page whose URL's host is "mozilla.org" or ends with - ".mozilla.org" - - Any page whose URL starts with "https:" */ - - /* make the above-mentioned pages really ugly */ - body { - color: purple; - background: yellow; - } - } - `; - - var ast = makeAST(styles); - - var rules = ast.rules; - var documentRule = rules[0]; - expect(documentRule.type).toEqual(BlockType.Document); - - var rule = documentRule.block.entries[0]; - expect(rule.strValue).toEqual("body"); - }); - - it('should parse the @page rule', () => { - var styles = ` - @page one { - .selector { prop: value; } - } - @page two { - .selector2 { prop: value2; } - } - `; - - var ast = makeAST(styles); - - var rules = ast.rules; - - var pageRule1 = rules[0]; - expect(pageRule1.strValue).toEqual("one"); - expect(pageRule1.type).toEqual(BlockType.Page); - - var pageRule2 = rules[1]; - expect(pageRule2.strValue).toEqual("two"); - expect(pageRule2.type).toEqual(BlockType.Page); - - var selectorOne = pageRule1.block.entries[0]; - expect(selectorOne.strValue).toEqual('.selector'); - - var selectorTwo = pageRule2.block.entries[0]; - expect(selectorTwo.strValue).toEqual('.selector2'); - }); - - it('should parse the @supports rule', () => { - var styles = ` - @supports (animation-name: "rotate") { - a:hover { animation: rotate 1s; } - } - `; - - var ast = makeAST(styles); - - var rules = ast.rules; - - var supportsRule = rules[0]; - assertTokens(supportsRule.query, ['(', 'animation-name', ':', 'rotate', ')']); - expect(supportsRule.type).toEqual(BlockType.Supports); - - var selectorOne = supportsRule.block.entries[0]; - expect(selectorOne.strValue).toEqual('a:hover'); - }); - - it('should collect multiple errors during parsing', () => { - var styles = ` - .class$value { something: something } - @custom { something: something } - #id { cool^: value } - `; - - var output = parse(styles); - expect(output.errors.length).toEqual(3); - }); - - it('should recover from selector errors and continue parsing', () => { - var styles = ` - tag& { key: value; } - .%tag { key: value; } - #tag$ { key: value; } - `; - - var output = parse(styles); - var errors = output.errors; - var ast = output.ast; - - expect(errors.length).toEqual(3); - - expect(ast.rules.length).toEqual(3); - - var rule1 = ast.rules[0]; - expect(rule1.selectors[0].strValue).toEqual("tag&"); - expect(rule1.block.entries.length).toEqual(1); - - var rule2 = ast.rules[1]; - expect(rule2.selectors[0].strValue).toEqual(".%tag"); - expect(rule2.block.entries.length).toEqual(1); - - var rule3 = ast.rules[2]; - expect(rule3.selectors[0].strValue).toEqual("#tag$"); - expect(rule3.block.entries.length).toEqual(1); - }); - - it('should throw an error when parsing invalid CSS Selectors', () => { - var styles = '.class[[prop%=value}] { style: val; }'; - var output = parse(styles); - var errors = output.errors; - - expect(errors.length).toEqual(3); - - expect(errors[0].msg).toMatch(/Unexpected character \[\[\] at column 0:7/g); - - expect(errors[1].msg).toMatch(/Unexpected character \[%\] at column 0:12/g); - - expect(errors[2].msg).toMatch(/Unexpected character \[}\] at column 0:19/g); - }); - - it('should throw an error if an attribute selector is not closed properly', () => { - var styles = '.class[prop=value { style: val; }'; - var output = parse(styles); - var errors = output.errors; - - expect(errors[0].msg).toMatch(/Unbalanced CSS attribute selector at column 0:12/g); - }); - - it('should throw an error if a pseudo function selector is not closed properly', () => { - var styles = 'body:lang(en { key:value; }'; - var output = parse(styles); - var errors = output.errors; - - expect(errors[0].msg).toMatch(/Unbalanced pseudo selector function value at column 0:10/g); - }); - - it('should raise an error when a semi colon is missing from a CSS style/pair that isn\'t the last entry', () => { - var styles = `.class { - color: red - background: blue - }`; - - var output = parse(styles); - var errors = output.errors; - - expect(errors.length).toEqual(1); - - expect(errors[0].msg) - .toMatch(/The CSS key\/value definition did not end with a semicolon at column 1:15/g); - }); - - it('should parse the inner value of a :not() pseudo-selector as a CSS selector', () => { - var styles = `div:not(.ignore-this-div) { - prop: value; - }`; - - var output = parse(styles); - var errors = output.errors; - var ast = output.ast; - - expect(errors.length).toEqual(0); - - var rule1 = ast.rules[0]; - expect(rule1.selectors.length).toEqual(1); - - var selector = rule1.selectors[0]; - assertTokens(selector.tokens, ['div',':','not','(','.','ignore-this-div',')']); - }); - - it('should raise parse errors when CSS key/value pairs are invalid', () => { - var styles = `.class { - background color: value; - color: value - font-size; - font-weight - }`; - - var output = parse(styles); - var errors = output.errors; - - expect(errors.length).toEqual(4); - - expect(errors[0].msg) - .toMatch( - /Identifier does not match expected Character value \("color" should match ":"\) at column 1:19/g); - - expect(errors[1].msg) - .toMatch(/The CSS key\/value definition did not end with a semicolon at column 2:15/g); - - expect(errors[2].msg) - .toMatch(/The CSS property was not paired with a style value at column 3:8/g); - - expect(errors[3].msg) - .toMatch(/The CSS property was not paired with a style value at column 4:8/g); - }); - - it('should recover from CSS key/value parse errors', () => { - var styles = ` - .problem-class { background color: red; color: white; } - .good-boy-class { background-color: red; color: white; } - `; - - var output = parse(styles); - var errors = output.errors; - var ast = output.ast; - - expect(ast.rules.length).toEqual(2); - - var rule1 = ast.rules[0]; - expect(rule1.block.entries.length).toEqual(2); - - var style1 = rule1.block.entries[0]; - expect(style1.property.strValue).toEqual('background color'); - assertTokens(style1.value.tokens, ['red']); - - var style2 = rule1.block.entries[1]; - expect(style2.property.strValue).toEqual('color'); - assertTokens(style2.value.tokens, ['white']); - }); - - it('should parse minified CSS content properly', () => { - // this code was taken from the angular.io webpage's CSS code - var styles = ` -.is-hidden{display:none!important} -.is-visible{display:block!important} -.is-visually-hidden{height:1px;width:1px;overflow:hidden;opacity:0.01;position:absolute;bottom:0;right:0;z-index:1} -.grid-fluid,.grid-fixed{margin:0 auto} -.grid-fluid .c1,.grid-fixed .c1,.grid-fluid .c2,.grid-fixed .c2,.grid-fluid .c3,.grid-fixed .c3,.grid-fluid .c4,.grid-fixed .c4,.grid-fluid .c5,.grid-fixed .c5,.grid-fluid .c6,.grid-fixed .c6,.grid-fluid .c7,.grid-fixed .c7,.grid-fluid .c8,.grid-fixed .c8,.grid-fluid .c9,.grid-fixed .c9,.grid-fluid .c10,.grid-fixed .c10,.grid-fluid .c11,.grid-fixed .c11,.grid-fluid .c12,.grid-fixed .c12{display:inline;float:left} -.grid-fluid .c1.grid-right,.grid-fixed .c1.grid-right,.grid-fluid .c2.grid-right,.grid-fixed .c2.grid-right,.grid-fluid .c3.grid-right,.grid-fixed .c3.grid-right,.grid-fluid .c4.grid-right,.grid-fixed .c4.grid-right,.grid-fluid .c5.grid-right,.grid-fixed .c5.grid-right,.grid-fluid .c6.grid-right,.grid-fixed .c6.grid-right,.grid-fluid .c7.grid-right,.grid-fixed .c7.grid-right,.grid-fluid .c8.grid-right,.grid-fixed .c8.grid-right,.grid-fluid .c9.grid-right,.grid-fixed .c9.grid-right,.grid-fluid .c10.grid-right,.grid-fixed .c10.grid-right,.grid-fluid .c11.grid-right,.grid-fixed .c11.grid-right,.grid-fluid .c12.grid-right,.grid-fixed .c12.grid-right{float:right} -.grid-fluid .c1.nb,.grid-fixed .c1.nb,.grid-fluid .c2.nb,.grid-fixed .c2.nb,.grid-fluid .c3.nb,.grid-fixed .c3.nb,.grid-fluid .c4.nb,.grid-fixed .c4.nb,.grid-fluid .c5.nb,.grid-fixed .c5.nb,.grid-fluid .c6.nb,.grid-fixed .c6.nb,.grid-fluid .c7.nb,.grid-fixed .c7.nb,.grid-fluid .c8.nb,.grid-fixed .c8.nb,.grid-fluid .c9.nb,.grid-fixed .c9.nb,.grid-fluid .c10.nb,.grid-fixed .c10.nb,.grid-fluid .c11.nb,.grid-fixed .c11.nb,.grid-fluid .c12.nb,.grid-fixed .c12.nb{margin-left:0} -.grid-fluid .c1.na,.grid-fixed .c1.na,.grid-fluid .c2.na,.grid-fixed .c2.na,.grid-fluid .c3.na,.grid-fixed .c3.na,.grid-fluid .c4.na,.grid-fixed .c4.na,.grid-fluid .c5.na,.grid-fixed .c5.na,.grid-fluid .c6.na,.grid-fixed .c6.na,.grid-fluid .c7.na,.grid-fixed .c7.na,.grid-fluid .c8.na,.grid-fixed .c8.na,.grid-fluid .c9.na,.grid-fixed .c9.na,.grid-fluid .c10.na,.grid-fixed .c10.na,.grid-fluid .c11.na,.grid-fixed .c11.na,.grid-fluid .c12.na,.grid-fixed .c12.na{margin-right:0} - `; - - var output = parse(styles); - var errors = output.errors; - expect(errors.length).toEqual(0); - - var ast = output.ast; - expect(ast.rules.length).toEqual(8); - }); - - it('should parse a snippet of keyframe code from animate.css properly', () => { - // this code was taken from the angular.io webpage's CSS code - var styles = ` -@charset "UTF-8"; - -/*! - * animate.css -http://daneden.me/animate - * Version - 3.5.1 - * Licensed under the MIT license - http://opensource.org/licenses/MIT - * - * Copyright (c) 2016 Daniel Eden - */ - -.animated { - -webkit-animation-duration: 1s; - animation-duration: 1s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; -} - -.animated.infinite { - -webkit-animation-iteration-count: infinite; - animation-iteration-count: infinite; -} - -.animated.hinge { - -webkit-animation-duration: 2s; - animation-duration: 2s; -} - -.animated.flipOutX, -.animated.flipOutY, -.animated.bounceIn, -.animated.bounceOut { - -webkit-animation-duration: .75s; - animation-duration: .75s; -} - -@-webkit-keyframes bounce { - from, 20%, 53%, 80%, to { - -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); - animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); - -webkit-transform: translate3d(0,0,0); - transform: translate3d(0,0,0); - } - - 40%, 43% { - -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); - animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); - -webkit-transform: translate3d(0, -30px, 0); - transform: translate3d(0, -30px, 0); - } - - 70% { - -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); - animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); - -webkit-transform: translate3d(0, -15px, 0); - transform: translate3d(0, -15px, 0); - } - - 90% { - -webkit-transform: translate3d(0,-4px,0); - transform: translate3d(0,-4px,0); - } -} - `; - - var output = parse(styles); - var errors = output.errors; - expect(errors.length).toEqual(0); - - var ast = output.ast; - expect(ast.rules.length).toEqual(6); - - var finalRule = ast.rules[ast.rules.length - 1]; - expect(finalRule.type).toEqual(BlockType.Keyframes); - expect(finalRule.block.entries.length).toEqual(4); - }); - }); -} diff --git a/modules/angular2/test/compiler/css/visitor_spec.ts b/modules/angular2/test/compiler/css/visitor_spec.ts deleted file mode 100644 index d97ff59f1c..0000000000 --- a/modules/angular2/test/compiler/css/visitor_spec.ts +++ /dev/null @@ -1,262 +0,0 @@ -import { - ddescribe, - describe, - it, - iit, - xit, - expect, - beforeEach, - afterEach -} from 'angular2/testing_internal'; - -export function assertTokens(tokens, valuesArr) { - for (var i = 0; i < tokens.length; i++) { - expect(tokens[i].strValue == valuesArr[i]); - } -} - -import {NumberWrapper, StringWrapper, isPresent} from "angular2/src/facade/lang"; -import {BaseException} from 'angular2/src/facade/exceptions'; - -import { - CssToken, - CssParser, - CssParseError, - BlockType, - CssAST, - CssSelectorRuleAST, - CssKeyframeRuleAST, - CssKeyframeDefinitionAST, - CssBlockDefinitionRuleAST, - CssMediaQueryRuleAST, - CssBlockRuleAST, - CssInlineRuleAST, - CssStyleValueAST, - CssSelectorAST, - CssDefinitionAST, - CssStyleSheetAST, - CssRuleAST, - CssBlockAST, - CssASTVisitor, - CssUnknownTokenListAST -} from 'angular2/src/compiler/css/parser'; - -import {CssLexer} from 'angular2/src/compiler/css/lexer'; - -class MyVisitor implements CssASTVisitor { - captures = {}; - - _capture(method, ast, context) { - this.captures[method] = isPresent(this.captures[method]) ? this.captures[method] : []; - this.captures[method].push([ast, context]); - } - - constructor(ast: CssStyleSheetAST, context) { - ast.visit(this, context); - } - - visitCssValue(ast, context): void { - this._capture("visitCssValue", ast, context); - } - - visitInlineCssRule(ast, context): void { - this._capture("visitInlineCssRule", ast, context); - } - - visitCssKeyframeRule(ast: CssKeyframeRuleAST, context): void { - this._capture("visitCssKeyframeRule", ast, context); - ast.block.visit(this, context); - } - - visitCssKeyframeDefinition(ast: CssKeyframeDefinitionAST, context?: any): void { - this._capture("visitCssKeyframeDefinition", ast, context); - ast.block.visit(this, context); - } - - visitCssMediaQueryRule(ast: CssMediaQueryRuleAST, context): void { - this._capture("visitCssMediaQueryRule", ast, context); - ast.block.visit(this, context); - } - - visitCssSelectorRule(ast: CssSelectorRuleAST, context): void { - this._capture("visitCssSelectorRule", ast, context); - ast.selectors.forEach((selAST: CssSelectorAST) => { - selAST.visit(this, context); - }); - ast.block.visit(this, context); - } - - visitCssSelector(ast: CssSelectorAST, context): void { - this._capture("visitCssSelector", ast, context); - } - - visitCssDefinition(ast: CssDefinitionAST, context): void { - this._capture("visitCssDefinition", ast, context); - ast.value.visit(this, context); - } - - visitCssBlock(ast: CssBlockAST, context): void { - this._capture("visitCssBlock", ast, context); - ast.entries.forEach((entryAST: CssAST) => { - entryAST.visit(this, context); - }); - } - - visitCssStyleSheet(ast, context): void { - this._capture("visitCssStyleSheet", ast, context); - ast.rules.forEach((ruleAST: CssSelectorRuleAST) => { - ruleAST.visit(this, context); - }); - } - - visitUnkownRule(ast: CssUnknownTokenListAST, context?: any): void { - // nothing - } -} - -export function main() { - function parse(cssCode: string) { - 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) { - throw new BaseException(errors.map((error: CssParseError) => error.msg).join(', ')); - } - return output.ast; - } - - describe('CSS parsing and visiting', () => { - var ast; - var context = {}; - - beforeEach(() => { - var cssCode = ` - .rule1 { prop1: value1 } - .rule2 { prop2: value2 } - - @media all (max-width: 100px) { - #id { prop3 :value3; } - } - - @import url(file.css); - - @keyframes rotate { - from { - prop4: value4; - } - 50%, 100% { - prop5: value5; - } - } - `; - ast = parse(cssCode); - }); - - it('should parse and visit a stylesheet', () => { - var visitor = new MyVisitor(ast, context); - var captures = visitor.captures['visitCssStyleSheet']; - - expect(captures.length).toEqual(1); - - var capture = captures[0]; - expect(capture[0]).toEqual(ast); - expect(capture[1]).toEqual(context); - }); - - it('should parse and visit each of the stylesheet selectors', () => { - var visitor = new MyVisitor(ast, context); - var captures = visitor.captures['visitCssSelectorRule']; - - expect(captures.length).toEqual(3); - - var rule1 = captures[0][0]; - expect(rule1).toEqual(ast.rules[0]); - assertTokens(rule1.selectors[0].tokens, ['.', 'rule1']); - - var rule2 = captures[1][0]; - expect(rule2).toEqual(ast.rules[1]); - assertTokens(rule2.selectors[0].tokens, ['.', 'rule2']); - - var rule3 = captures[2][0]; - expect(rule3).toEqual((ast.rules[2]).block.entries[0]); - assertTokens(rule3.selectors[0].tokens, ['#', 'rule3']); - }); - - it('should parse and visit each of the stylesheet style key/value definitions', () => { - var visitor = new MyVisitor(ast, context); - var captures = visitor.captures['visitCssDefinition']; - - expect(captures.length).toEqual(5); - - var def1 = captures[0][0]; - expect(def1.property.strValue).toEqual('prop1'); - expect(def1.value.tokens[0].strValue).toEqual('value1'); - - var def2 = captures[1][0]; - expect(def2.property.strValue).toEqual('prop2'); - expect(def2.value.tokens[0].strValue).toEqual('value2'); - - var def3 = captures[2][0]; - expect(def3.property.strValue).toEqual('prop3'); - expect(def3.value.tokens[0].strValue).toEqual('value3'); - - var def4 = captures[3][0]; - expect(def4.property.strValue).toEqual('prop4'); - expect(def4.value.tokens[0].strValue).toEqual('value4'); - - var def5 = captures[4][0]; - expect(def5.property.strValue).toEqual('prop5'); - expect(def5.value.tokens[0].strValue).toEqual('value5'); - }); - - it('should parse and visit the associated media query values', () => { - var visitor = new MyVisitor(ast, context); - var captures = visitor.captures['visitCssMediaQueryRule']; - - expect(captures.length).toEqual(1); - - var query1 = captures[0][0]; - assertTokens(query1.query, ['all','(','max-width','100','px',')']); - expect(query1.block.entries.length).toEqual(1); - }); - - it('should parse and visit the associated "@inline" rule values', () => { - var visitor = new MyVisitor(ast, context); - var captures = visitor.captures['visitInlineCssRule']; - - expect(captures.length).toEqual(1); - - var query1 = captures[0][0]; - expect(query1.type).toEqual(BlockType.Import); - assertTokens(query1.value.tokens, ['url','(','file.css',')']); - }); - - it('should parse and visit the keyframe blocks', () => { - var visitor = new MyVisitor(ast, context); - var captures = visitor.captures['visitCssKeyframeRule']; - - expect(captures.length).toEqual(1); - - var keyframe1 = captures[0][0]; - expect(keyframe1.name.strValue).toEqual('rotate'); - expect(keyframe1.block.entries.length).toEqual(2); - }); - - it('should parse and visit the associated keyframe rules', () => { - var visitor = new MyVisitor(ast, context); - var captures = visitor.captures['visitCssKeyframeDefinition']; - - expect(captures.length).toEqual(2); - - var def1 = captures[0][0]; - assertTokens(def1.steps, ['from']); - expect(def1.block.entries.length).toEqual(1); - - var def2 = captures[1][0]; - assertTokens(def2.steps, ['50%', '100%']); - expect(def2.block.entries.length).toEqual(1); - }); - }); -}