2016-06-21 11:10:25 -07:00
|
|
|
import * as chars from './chars';
|
|
|
|
import {CssLexerMode, CssScanner, CssToken, CssTokenType, generateErrorMessage, isNewline} from './css_lexer';
|
|
|
|
import {isPresent} from './facade/lang';
|
2016-06-16 11:39:29 -07:00
|
|
|
import {ParseError, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
|
2016-06-14 16:26:57 -07:00
|
|
|
|
|
|
|
const SPACE_OPERATOR = ' ';
|
2016-04-12 09:40:37 -07:00
|
|
|
|
2016-06-16 11:39:29 -07:00
|
|
|
export {CssToken} from './css_lexer';
|
2016-02-02 10:37:08 +02:00
|
|
|
|
|
|
|
export enum BlockType {
|
|
|
|
Import,
|
|
|
|
Charset,
|
|
|
|
Namespace,
|
|
|
|
Supports,
|
|
|
|
Keyframes,
|
|
|
|
MediaQuery,
|
|
|
|
Selector,
|
|
|
|
FontFace,
|
|
|
|
Page,
|
|
|
|
Document,
|
|
|
|
Viewport,
|
|
|
|
Unsupported
|
|
|
|
}
|
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
const SLASH_CHARACTER = '/';
|
|
|
|
const GT_CHARACTER = '>';
|
|
|
|
const TRIPLE_GT_OPERATOR_STR = '>>>';
|
|
|
|
const DEEP_OPERATOR_STR = '/deep/';
|
|
|
|
|
|
|
|
const EOF_DELIM_FLAG = 1;
|
|
|
|
const RBRACE_DELIM_FLAG = 2;
|
|
|
|
const LBRACE_DELIM_FLAG = 4;
|
|
|
|
const COMMA_DELIM_FLAG = 8;
|
|
|
|
const COLON_DELIM_FLAG = 16;
|
|
|
|
const SEMICOLON_DELIM_FLAG = 32;
|
|
|
|
const NEWLINE_DELIM_FLAG = 64;
|
|
|
|
const RPAREN_DELIM_FLAG = 128;
|
|
|
|
const LPAREN_DELIM_FLAG = 256;
|
|
|
|
const SPACE_DELIM_FLAG = 512;
|
|
|
|
|
|
|
|
function _pseudoSelectorSupportsInnerSelectors(name: string): boolean {
|
|
|
|
return ['not', 'host', 'host-context'].indexOf(name) >= 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
function isSelectorOperatorCharacter(code: number): boolean {
|
|
|
|
switch (code) {
|
2016-06-21 11:10:25 -07:00
|
|
|
case chars.$SLASH:
|
|
|
|
case chars.$TILDA:
|
|
|
|
case chars.$PLUS:
|
|
|
|
case chars.$GT:
|
2016-06-14 16:26:57 -07:00
|
|
|
return true;
|
|
|
|
default:
|
2016-06-21 11:10:25 -07:00
|
|
|
return chars.isWhitespace(code);
|
2016-06-14 16:26:57 -07:00
|
|
|
}
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-08 16:38:52 -07:00
|
|
|
function mergeTokens(tokens: CssToken[], separator: string = ''): CssToken {
|
2016-02-02 10:37:08 +02:00
|
|
|
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) {
|
2016-06-21 11:10:25 -07:00
|
|
|
case chars.$EOF:
|
2016-06-14 16:26:57 -07:00
|
|
|
return EOF_DELIM_FLAG;
|
2016-06-21 11:10:25 -07:00
|
|
|
case chars.$COMMA:
|
2016-06-14 16:26:57 -07:00
|
|
|
return COMMA_DELIM_FLAG;
|
2016-06-21 11:10:25 -07:00
|
|
|
case chars.$COLON:
|
2016-06-14 16:26:57 -07:00
|
|
|
return COLON_DELIM_FLAG;
|
2016-06-21 11:10:25 -07:00
|
|
|
case chars.$SEMICOLON:
|
2016-06-14 16:26:57 -07:00
|
|
|
return SEMICOLON_DELIM_FLAG;
|
2016-06-21 11:10:25 -07:00
|
|
|
case chars.$RBRACE:
|
2016-06-14 16:26:57 -07:00
|
|
|
return RBRACE_DELIM_FLAG;
|
2016-06-21 11:10:25 -07:00
|
|
|
case chars.$LBRACE:
|
2016-06-14 16:26:57 -07:00
|
|
|
return LBRACE_DELIM_FLAG;
|
2016-06-21 11:10:25 -07:00
|
|
|
case chars.$RPAREN:
|
2016-06-14 16:26:57 -07:00
|
|
|
return RPAREN_DELIM_FLAG;
|
2016-06-21 11:10:25 -07:00
|
|
|
case chars.$SPACE:
|
|
|
|
case chars.$TAB:
|
2016-06-14 16:26:57 -07:00
|
|
|
return SPACE_DELIM_FLAG;
|
2016-02-02 10:37:08 +02:00
|
|
|
default:
|
2016-06-14 16:26:57 -07:00
|
|
|
return isNewline(code) ? NEWLINE_DELIM_FLAG : 0;
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
function characterContainsDelimiter(code: number, delimiters: number): boolean {
|
2016-06-16 11:54:20 -07:00
|
|
|
return (getDelimFromCharacter(code) & delimiters) > 0;
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export abstract class CssAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
constructor(public start: number, public end: number) {}
|
2016-06-16 11:20:27 -07:00
|
|
|
abstract visit(visitor: CssAstVisitor, context?: any): any;
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
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;
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export class ParsedCssResult {
|
2016-06-16 11:20:27 -07:00
|
|
|
constructor(public errors: CssParseError[], public ast: CssStyleSheetAst) {}
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export class CssParser {
|
|
|
|
private _errors: CssParseError[] = [];
|
|
|
|
private _file: ParseSourceFile;
|
|
|
|
|
|
|
|
constructor(private _scanner: CssScanner, private _fileName: string) {
|
|
|
|
this._file = new ParseSourceFile(this._scanner.input, _fileName);
|
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-02-02 10:37:08 +02:00
|
|
|
_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 {
|
2016-06-14 16:26:57 -07:00
|
|
|
var delimiters: number = EOF_DELIM_FLAG;
|
2016-02-02 10:37:08 +02:00
|
|
|
var ast = this._parseStyleSheet(delimiters);
|
|
|
|
|
|
|
|
var errors = this._errors;
|
|
|
|
this._errors = [];
|
|
|
|
|
|
|
|
return new ParsedCssResult(errors, ast);
|
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-06-16 11:20:27 -07:00
|
|
|
_parseStyleSheet(delimiters: number): CssStyleSheetAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
const start = this._getScannerIndex();
|
2016-06-08 15:45:15 -07:00
|
|
|
var results: any[] /** TODO #9100 */ = [];
|
2016-02-02 10:37:08 +02:00
|
|
|
this._scanner.consumeEmptyStatements();
|
2016-06-21 11:10:25 -07:00
|
|
|
while (this._scanner.peek != chars.$EOF) {
|
2016-02-02 10:37:08 +02:00
|
|
|
this._scanner.setMode(CssLexerMode.BLOCK);
|
|
|
|
results.push(this._parseRule(delimiters));
|
|
|
|
}
|
2016-06-14 16:26:57 -07:00
|
|
|
const end = this._getScannerIndex() - 1;
|
2016-06-16 11:20:27 -07:00
|
|
|
return new CssStyleSheetAst(start, end, results);
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-06-16 11:20:27 -07:00
|
|
|
_parseRule(delimiters: number): CssRuleAst {
|
2016-06-21 11:10:25 -07:00
|
|
|
if (this._scanner.peek == chars.$AT) {
|
2016-02-02 10:37:08 +02:00
|
|
|
return this._parseAtRule(delimiters);
|
|
|
|
}
|
|
|
|
return this._parseSelectorRule(delimiters);
|
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-06-16 11:20:27 -07:00
|
|
|
_parseAtRule(delimiters: number): CssRuleAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
const start = this._getScannerIndex();
|
|
|
|
var end: number;
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
this._scanner.setMode(CssLexerMode.BLOCK);
|
2016-02-02 10:37:08 +02:00
|
|
|
var token = this._scan();
|
|
|
|
|
2016-06-08 16:38:52 -07:00
|
|
|
this._assertCondition(
|
|
|
|
token.type == CssTokenType.AtKeyword,
|
|
|
|
`The CSS Rule ${token.strValue} is not a valid [@] rule.`, token);
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
var block: CssBlockAst;
|
2016-06-14 16:26:57 -07:00
|
|
|
var type = this._resolveBlockType(token);
|
2016-02-02 10:37:08 +02:00
|
|
|
switch (type) {
|
|
|
|
case BlockType.Charset:
|
|
|
|
case BlockType.Namespace:
|
|
|
|
case BlockType.Import:
|
|
|
|
var value = this._parseValue(delimiters);
|
|
|
|
this._scanner.setMode(CssLexerMode.BLOCK);
|
2016-06-14 16:26:57 -07:00
|
|
|
end = value.end;
|
2016-02-02 10:37:08 +02:00
|
|
|
this._scanner.consumeEmptyStatements();
|
2016-06-16 11:20:27 -07:00
|
|
|
return new CssInlineRuleAst(start, end, type, value);
|
2016-02-02 10:37:08 +02:00
|
|
|
|
|
|
|
case BlockType.Viewport:
|
|
|
|
case BlockType.FontFace:
|
|
|
|
block = this._parseStyleBlock(delimiters);
|
2016-06-14 16:26:57 -07:00
|
|
|
end = this._getScannerIndex() - 1;
|
2016-06-16 11:20:27 -07:00
|
|
|
return new CssBlockRuleAst(start, end, type, block);
|
2016-02-02 10:37:08 +02:00
|
|
|
|
|
|
|
case BlockType.Keyframes:
|
2016-06-16 11:54:20 -07:00
|
|
|
var tokens = this._collectUntilDelim(delimiters | RBRACE_DELIM_FLAG | LBRACE_DELIM_FLAG);
|
2016-02-02 10:37:08 +02:00
|
|
|
// keyframes only have one identifier name
|
|
|
|
var name = tokens[0];
|
2016-06-14 16:26:57 -07:00
|
|
|
end = this._getScannerIndex() - 1;
|
2016-06-16 11:20:27 -07:00
|
|
|
return new CssKeyframeRuleAst(start, end, name, this._parseKeyframeBlock(delimiters));
|
2016-02-02 10:37:08 +02:00
|
|
|
|
|
|
|
case BlockType.MediaQuery:
|
|
|
|
this._scanner.setMode(CssLexerMode.MEDIA_QUERY);
|
2016-06-16 11:54:20 -07:00
|
|
|
var tokens = this._collectUntilDelim(delimiters | RBRACE_DELIM_FLAG | LBRACE_DELIM_FLAG);
|
2016-06-14 16:26:57 -07:00
|
|
|
end = this._getScannerIndex() - 1;
|
|
|
|
var strValue = this._scanner.input.substring(start, end);
|
2016-06-16 11:20:27 -07:00
|
|
|
var query = new CssAtRulePredicateAst(start, end, strValue, tokens);
|
2016-06-14 16:26:57 -07:00
|
|
|
block = this._parseBlock(delimiters);
|
|
|
|
end = this._getScannerIndex() - 1;
|
|
|
|
strValue = this._scanner.input.substring(start, end);
|
2016-06-16 11:20:27 -07:00
|
|
|
return new CssMediaQueryRuleAst(start, end, strValue, query, block);
|
2016-02-02 10:37:08 +02:00
|
|
|
|
|
|
|
case BlockType.Document:
|
|
|
|
case BlockType.Supports:
|
|
|
|
case BlockType.Page:
|
|
|
|
this._scanner.setMode(CssLexerMode.AT_RULE_QUERY);
|
2016-06-16 11:54:20 -07:00
|
|
|
var tokens = this._collectUntilDelim(delimiters | RBRACE_DELIM_FLAG | LBRACE_DELIM_FLAG);
|
2016-06-14 16:26:57 -07:00
|
|
|
end = this._getScannerIndex() - 1;
|
|
|
|
var strValue = this._scanner.input.substring(start, end);
|
2016-06-16 11:20:27 -07:00
|
|
|
var query = new CssAtRulePredicateAst(start, end, strValue, tokens);
|
2016-06-14 16:26:57 -07:00
|
|
|
block = this._parseBlock(delimiters);
|
|
|
|
end = this._getScannerIndex() - 1;
|
|
|
|
strValue = this._scanner.input.substring(start, end);
|
2016-06-16 11:20:27 -07:00
|
|
|
return new CssBlockDefinitionRuleAst(start, end, strValue, type, query, block);
|
2016-02-02 10:37:08 +02:00
|
|
|
|
|
|
|
// if a custom @rule { ... } is used it should still tokenize the insides
|
|
|
|
default:
|
2016-06-14 16:26:57 -07:00
|
|
|
var listOfTokens: CssToken[] = [];
|
|
|
|
var tokenName = token.strValue;
|
2016-02-02 10:37:08 +02:00
|
|
|
this._scanner.setMode(CssLexerMode.ALL);
|
2016-06-08 16:38:52 -07:00
|
|
|
this._error(
|
|
|
|
generateErrorMessage(
|
2016-06-14 16:26:57 -07:00
|
|
|
this._scanner.input, `The CSS "at" rule "${tokenName}" is not allowed to used here`,
|
|
|
|
token.strValue, token.index, token.line, token.column),
|
2016-06-08 16:38:52 -07:00
|
|
|
token);
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-16 11:54:20 -07:00
|
|
|
this._collectUntilDelim(delimiters | LBRACE_DELIM_FLAG | SEMICOLON_DELIM_FLAG)
|
2016-02-02 10:37:08 +02:00
|
|
|
.forEach((token) => { listOfTokens.push(token); });
|
2016-06-21 11:10:25 -07:00
|
|
|
if (this._scanner.peek == chars.$LBRACE) {
|
2016-06-14 16:26:57 -07:00
|
|
|
listOfTokens.push(this._consume(CssTokenType.Character, '{'));
|
2016-06-16 11:54:20 -07:00
|
|
|
this._collectUntilDelim(delimiters | RBRACE_DELIM_FLAG | LBRACE_DELIM_FLAG)
|
2016-02-02 10:37:08 +02:00
|
|
|
.forEach((token) => { listOfTokens.push(token); });
|
2016-06-14 16:26:57 -07:00
|
|
|
listOfTokens.push(this._consume(CssTokenType.Character, '}'));
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
2016-06-14 16:26:57 -07:00
|
|
|
end = this._getScannerIndex() - 1;
|
2016-06-16 11:20:27 -07:00
|
|
|
return new CssUnknownRuleAst(start, end, tokenName, listOfTokens);
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-06-16 11:20:27 -07:00
|
|
|
_parseSelectorRule(delimiters: number): CssRuleAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
const start = this._getScannerIndex();
|
2016-02-02 10:37:08 +02:00
|
|
|
var selectors = this._parseSelectors(delimiters);
|
|
|
|
var block = this._parseStyleBlock(delimiters);
|
2016-06-14 16:26:57 -07:00
|
|
|
const end = this._getScannerIndex() - 1;
|
2016-06-16 11:20:27 -07:00
|
|
|
var token: CssRuleAst;
|
2016-06-14 16:26:57 -07:00
|
|
|
if (isPresent(block)) {
|
2016-06-16 11:20:27 -07:00
|
|
|
token = new CssSelectorRuleAst(start, end, selectors, block);
|
2016-06-14 16:26:57 -07:00
|
|
|
} else {
|
|
|
|
var name = this._scanner.input.substring(start, end);
|
|
|
|
var innerTokens: CssToken[] = [];
|
2016-06-16 11:20:27 -07:00
|
|
|
selectors.forEach((selector: CssSelectorAst) => {
|
|
|
|
selector.selectorParts.forEach((part: CssSimpleSelectorAst) => {
|
2016-06-14 16:26:57 -07:00
|
|
|
part.tokens.forEach((token: CssToken) => { innerTokens.push(token); });
|
|
|
|
});
|
|
|
|
});
|
2016-06-16 11:20:27 -07:00
|
|
|
token = new CssUnknownTokenListAst(start, end, name, innerTokens);
|
2016-06-14 16:26:57 -07:00
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
this._scanner.setMode(CssLexerMode.BLOCK);
|
|
|
|
this._scanner.consumeEmptyStatements();
|
2016-06-14 16:26:57 -07:00
|
|
|
return token;
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-06-16 11:20:27 -07:00
|
|
|
_parseSelectors(delimiters: number): CssSelectorAst[] {
|
2016-06-16 11:54:20 -07:00
|
|
|
delimiters |= LBRACE_DELIM_FLAG | SEMICOLON_DELIM_FLAG;
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-08 15:45:15 -07:00
|
|
|
var selectors: any[] /** TODO #9100 */ = [];
|
2016-02-02 10:37:08 +02:00
|
|
|
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);
|
2016-06-14 16:26:57 -07:00
|
|
|
if (isParsingSelectors) {
|
|
|
|
this._scanner.consumeWhitespace();
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return selectors;
|
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-02-02 10:37:08 +02:00
|
|
|
_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;
|
|
|
|
}
|
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
/** @internal */
|
|
|
|
_getScannerIndex(): number { return this._scanner.index; }
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-02-02 10:37:08 +02:00
|
|
|
_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;
|
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-06-16 11:20:27 -07:00
|
|
|
_parseKeyframeBlock(delimiters: number): CssBlockAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
const start = this._getScannerIndex();
|
|
|
|
|
2016-06-16 11:54:20 -07:00
|
|
|
delimiters |= RBRACE_DELIM_FLAG;
|
2016-02-02 10:37:08 +02:00
|
|
|
this._scanner.setMode(CssLexerMode.KEYFRAME_BLOCK);
|
|
|
|
|
|
|
|
this._consume(CssTokenType.Character, '{');
|
|
|
|
|
2016-06-08 15:45:15 -07:00
|
|
|
var definitions: any[] /** TODO #9100 */ = [];
|
2016-02-02 10:37:08 +02:00
|
|
|
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
|
|
|
definitions.push(this._parseKeyframeDefinition(delimiters));
|
|
|
|
}
|
|
|
|
|
|
|
|
this._consume(CssTokenType.Character, '}');
|
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
const end = this._getScannerIndex() - 1;
|
2016-06-16 11:20:27 -07:00
|
|
|
return new CssBlockAst(start, end, definitions);
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-06-16 11:20:27 -07:00
|
|
|
_parseKeyframeDefinition(delimiters: number): CssKeyframeDefinitionAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
const start = this._getScannerIndex();
|
2016-06-08 15:45:15 -07:00
|
|
|
var stepTokens: any[] /** TODO #9100 */ = [];
|
2016-06-16 11:54:20 -07:00
|
|
|
delimiters |= LBRACE_DELIM_FLAG;
|
2016-02-02 10:37:08 +02:00
|
|
|
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
2016-06-16 11:54:20 -07:00
|
|
|
stepTokens.push(this._parseKeyframeLabel(delimiters | COMMA_DELIM_FLAG));
|
2016-06-21 11:10:25 -07:00
|
|
|
if (this._scanner.peek != chars.$LBRACE) {
|
2016-02-02 10:37:08 +02:00
|
|
|
this._consume(CssTokenType.Character, ',');
|
|
|
|
}
|
|
|
|
}
|
2016-06-16 11:54:20 -07:00
|
|
|
var styles = this._parseStyleBlock(delimiters | RBRACE_DELIM_FLAG);
|
2016-02-02 10:37:08 +02:00
|
|
|
this._scanner.setMode(CssLexerMode.BLOCK);
|
2016-06-14 16:26:57 -07:00
|
|
|
const end = this._getScannerIndex() - 1;
|
2016-06-16 11:20:27 -07:00
|
|
|
return new CssKeyframeDefinitionAst(start, end, stepTokens, styles);
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-02-02 10:37:08 +02:00
|
|
|
_parseKeyframeLabel(delimiters: number): CssToken {
|
|
|
|
this._scanner.setMode(CssLexerMode.KEYFRAME_BLOCK);
|
|
|
|
return mergeTokens(this._collectUntilDelim(delimiters));
|
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-06-16 11:20:27 -07:00
|
|
|
_parsePseudoSelector(delimiters: number): CssPseudoSelectorAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
const start = this._getScannerIndex();
|
|
|
|
|
2016-06-16 11:54:20 -07:00
|
|
|
delimiters &= ~COMMA_DELIM_FLAG;
|
2016-06-14 16:26:57 -07:00
|
|
|
|
|
|
|
// we keep the original value since we may use it to recurse when :not, :host are used
|
|
|
|
var startingDelims = delimiters;
|
|
|
|
|
|
|
|
var startToken = this._consume(CssTokenType.Character, ':');
|
|
|
|
var tokens = [startToken];
|
|
|
|
|
2016-06-21 11:10:25 -07:00
|
|
|
if (this._scanner.peek == chars.$COLON) { // ::something
|
2016-06-14 16:26:57 -07:00
|
|
|
startToken = this._consume(CssTokenType.Character, ':');
|
|
|
|
tokens.push(startToken);
|
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
var innerSelectors: CssSelectorAst[] = [];
|
2016-06-14 16:26:57 -07:00
|
|
|
|
|
|
|
this._scanner.setMode(CssLexerMode.PSEUDO_SELECTOR);
|
|
|
|
|
|
|
|
// host, host-context, lang, not, nth-child are all identifiers
|
|
|
|
var pseudoSelectorToken = this._consume(CssTokenType.Identifier);
|
|
|
|
var pseudoSelectorName = pseudoSelectorToken.strValue;
|
|
|
|
tokens.push(pseudoSelectorToken);
|
|
|
|
|
|
|
|
// host(), lang(), nth-child(), etc...
|
2016-06-21 11:10:25 -07:00
|
|
|
if (this._scanner.peek == chars.$LPAREN) {
|
2016-06-14 16:26:57 -07:00
|
|
|
this._scanner.setMode(CssLexerMode.PSEUDO_SELECTOR_WITH_ARGUMENTS);
|
|
|
|
|
|
|
|
var openParenToken = this._consume(CssTokenType.Character, '(');
|
|
|
|
tokens.push(openParenToken);
|
|
|
|
|
|
|
|
// :host(innerSelector(s)), :not(selector), etc...
|
|
|
|
if (_pseudoSelectorSupportsInnerSelectors(pseudoSelectorName)) {
|
2016-06-16 11:54:20 -07:00
|
|
|
var innerDelims = startingDelims | LPAREN_DELIM_FLAG | RPAREN_DELIM_FLAG;
|
2016-06-14 16:26:57 -07:00
|
|
|
if (pseudoSelectorName == 'not') {
|
|
|
|
// the inner selector inside of :not(...) can only be one
|
|
|
|
// CSS selector (no commas allowed) ... This is according
|
|
|
|
// to the CSS specification
|
2016-06-16 11:54:20 -07:00
|
|
|
innerDelims |= COMMA_DELIM_FLAG;
|
2016-06-14 16:26:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// :host(a, b, c) {
|
|
|
|
this._parseSelectors(innerDelims).forEach((selector, index) => {
|
|
|
|
innerSelectors.push(selector);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// this branch is for things like "en-us, 2k + 1, etc..."
|
|
|
|
// which all end up in pseudoSelectors like :lang, :nth-child, etc..
|
2016-06-16 11:54:20 -07:00
|
|
|
var innerValueDelims = delimiters | LBRACE_DELIM_FLAG | COLON_DELIM_FLAG |
|
|
|
|
RPAREN_DELIM_FLAG | LPAREN_DELIM_FLAG;
|
2016-06-14 16:26:57 -07:00
|
|
|
while (!characterContainsDelimiter(this._scanner.peek, innerValueDelims)) {
|
|
|
|
var token = this._scan();
|
|
|
|
tokens.push(token);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var closeParenToken = this._consume(CssTokenType.Character, ')');
|
|
|
|
tokens.push(closeParenToken);
|
|
|
|
}
|
|
|
|
|
|
|
|
const end = this._getScannerIndex() - 1;
|
|
|
|
var strValue = this._scanner.input.substring(start, end);
|
2016-06-16 11:20:27 -07:00
|
|
|
return new CssPseudoSelectorAst(
|
2016-06-14 16:26:57 -07:00
|
|
|
start, end, strValue, pseudoSelectorName, tokens, innerSelectors);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @internal */
|
2016-06-16 11:20:27 -07:00
|
|
|
_parseSimpleSelector(delimiters: number): CssSimpleSelectorAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
const start = this._getScannerIndex();
|
|
|
|
|
2016-06-16 11:54:20 -07:00
|
|
|
delimiters |= COMMA_DELIM_FLAG;
|
2016-06-14 16:26:57 -07:00
|
|
|
|
2016-02-02 10:37:08 +02:00
|
|
|
this._scanner.setMode(CssLexerMode.SELECTOR);
|
2016-06-14 16:26:57 -07:00
|
|
|
var selectorCssTokens: CssToken[] = [];
|
2016-06-16 11:20:27 -07:00
|
|
|
var pseudoSelectors: CssPseudoSelectorAst[] = [];
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
var previousToken: CssToken;
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-16 11:54:20 -07:00
|
|
|
var selectorPartDelimiters = delimiters | SPACE_DELIM_FLAG;
|
2016-06-14 16:26:57 -07:00
|
|
|
var loopOverSelector = !characterContainsDelimiter(this._scanner.peek, selectorPartDelimiters);
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
var hasAttributeError = false;
|
|
|
|
while (loopOverSelector) {
|
|
|
|
var peek = this._scanner.peek;
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
switch (peek) {
|
2016-06-21 11:10:25 -07:00
|
|
|
case chars.$COLON:
|
2016-06-14 16:26:57 -07:00
|
|
|
var innerPseudo = this._parsePseudoSelector(delimiters);
|
|
|
|
pseudoSelectors.push(innerPseudo);
|
|
|
|
this._scanner.setMode(CssLexerMode.SELECTOR);
|
|
|
|
break;
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-21 11:10:25 -07:00
|
|
|
case chars.$LBRACKET:
|
2016-06-14 16:26:57 -07:00
|
|
|
// we set the mode after the scan because attribute mode does not
|
|
|
|
// allow attribute [] values. And this also will catch any errors
|
|
|
|
// if an extra "[" is used inside.
|
|
|
|
selectorCssTokens.push(this._scan());
|
|
|
|
this._scanner.setMode(CssLexerMode.ATTRIBUTE_SELECTOR);
|
2016-02-02 10:37:08 +02:00
|
|
|
break;
|
|
|
|
|
2016-06-21 11:10:25 -07:00
|
|
|
case chars.$RBRACKET:
|
2016-06-14 16:26:57 -07:00
|
|
|
if (this._scanner.getMode() != CssLexerMode.ATTRIBUTE_SELECTOR) {
|
|
|
|
hasAttributeError = true;
|
|
|
|
}
|
|
|
|
// we set the mode early because attribute mode does not
|
|
|
|
// allow attribute [] values
|
2016-02-02 10:37:08 +02:00
|
|
|
this._scanner.setMode(CssLexerMode.SELECTOR);
|
2016-06-14 16:26:57 -07:00
|
|
|
selectorCssTokens.push(this._scan());
|
|
|
|
break;
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
default:
|
|
|
|
if (isSelectorOperatorCharacter(peek)) {
|
|
|
|
loopOverSelector = false;
|
|
|
|
continue;
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
var token = this._scan();
|
|
|
|
previousToken = token;
|
|
|
|
selectorCssTokens.push(token);
|
|
|
|
break;
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
loopOverSelector = !characterContainsDelimiter(this._scanner.peek, selectorPartDelimiters);
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
hasAttributeError =
|
|
|
|
hasAttributeError || this._scanner.getMode() == CssLexerMode.ATTRIBUTE_SELECTOR;
|
|
|
|
if (hasAttributeError) {
|
|
|
|
this._error(
|
|
|
|
`Unbalanced CSS attribute selector at column ${previousToken.line}:${previousToken.column}`,
|
|
|
|
previousToken);
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
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;
|
|
|
|
while (operator == null && !characterContainsDelimiter(this._scanner.peek, delimiters) &&
|
|
|
|
isSelectorOperatorCharacter(this._scanner.peek)) {
|
|
|
|
var token = this._scan();
|
|
|
|
var tokenOperator = token.strValue;
|
|
|
|
operatorScanCount++;
|
|
|
|
lastOperatorToken = token;
|
|
|
|
if (tokenOperator != SPACE_OPERATOR) {
|
|
|
|
switch (tokenOperator) {
|
|
|
|
case SLASH_CHARACTER:
|
|
|
|
// /deep/ operator
|
|
|
|
let deepToken = this._consume(CssTokenType.Identifier);
|
|
|
|
let deepSlash = this._consume(CssTokenType.Character);
|
|
|
|
let index = lastOperatorToken.index;
|
|
|
|
let line = lastOperatorToken.line;
|
|
|
|
let column = lastOperatorToken.column;
|
|
|
|
if (isPresent(deepToken) && deepToken.strValue.toLowerCase() == 'deep' &&
|
|
|
|
deepSlash.strValue == SLASH_CHARACTER) {
|
|
|
|
token = new CssToken(
|
|
|
|
lastOperatorToken.index, lastOperatorToken.column, lastOperatorToken.line,
|
|
|
|
CssTokenType.Identifier, DEEP_OPERATOR_STR);
|
|
|
|
} else {
|
|
|
|
let text = SLASH_CHARACTER + deepToken.strValue + deepSlash.strValue;
|
|
|
|
this._error(
|
|
|
|
generateErrorMessage(
|
|
|
|
this._scanner.input, `${text} is an invalid CSS operator`, text, index,
|
|
|
|
line, column),
|
|
|
|
lastOperatorToken);
|
|
|
|
token = new CssToken(index, column, line, CssTokenType.Invalid, text);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GT_CHARACTER:
|
|
|
|
// >>> operator
|
2016-06-21 11:10:25 -07:00
|
|
|
if (this._scanner.peek == chars.$GT && this._scanner.peekPeek == chars.$GT) {
|
2016-06-14 16:26:57 -07:00
|
|
|
this._consume(CssTokenType.Character, GT_CHARACTER);
|
|
|
|
this._consume(CssTokenType.Character, GT_CHARACTER);
|
|
|
|
token = new CssToken(
|
|
|
|
lastOperatorToken.index, lastOperatorToken.column, lastOperatorToken.line,
|
|
|
|
CssTokenType.Identifier, TRIPLE_GT_OPERATOR_STR);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
operator = token;
|
|
|
|
end = this._getScannerIndex() - 1;
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
this._scanner.consumeWhitespace();
|
|
|
|
|
|
|
|
// 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
|
2016-06-21 11:10:25 -07:00
|
|
|
if (operator == null && operatorScanCount > 0 && this._scanner.peek != chars.$LBRACE) {
|
2016-06-14 16:26:57 -07:00
|
|
|
operator = lastOperatorToken;
|
|
|
|
}
|
|
|
|
|
|
|
|
var strValue = this._scanner.input.substring(start, end);
|
2016-06-16 11:20:27 -07:00
|
|
|
return new CssSimpleSelectorAst(
|
2016-06-14 16:26:57 -07:00
|
|
|
start, end, selectorCssTokens, strValue, pseudoSelectors, operator);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @internal */
|
2016-06-16 11:20:27 -07:00
|
|
|
_parseSelector(delimiters: number): CssSelectorAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
const start = this._getScannerIndex();
|
|
|
|
|
2016-06-16 11:54:20 -07:00
|
|
|
delimiters |= COMMA_DELIM_FLAG;
|
2016-06-14 16:26:57 -07:00
|
|
|
this._scanner.setMode(CssLexerMode.SELECTOR);
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
var simpleSelectors: CssSimpleSelectorAst[] = [];
|
2016-06-14 16:26:57 -07:00
|
|
|
var end = this._getScannerIndex() - 1;
|
|
|
|
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
|
|
|
simpleSelectors.push(this._parseSimpleSelector(delimiters));
|
|
|
|
this._scanner.consumeWhitespace();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
return new CssSelectorAst(start, end, simpleSelectors);
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-06-16 11:20:27 -07:00
|
|
|
_parseValue(delimiters: number): CssStyleValueAst {
|
2016-06-16 11:54:20 -07:00
|
|
|
delimiters |= RBRACE_DELIM_FLAG | SEMICOLON_DELIM_FLAG | NEWLINE_DELIM_FLAG;
|
2016-02-02 10:37:08 +02:00
|
|
|
|
|
|
|
this._scanner.setMode(CssLexerMode.STYLE_VALUE);
|
2016-06-14 16:26:57 -07:00
|
|
|
const start = this._getScannerIndex();
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
var tokens: CssToken[] = [];
|
|
|
|
var wsStr = '';
|
2016-02-02 10:37:08 +02:00
|
|
|
var previous: CssToken;
|
|
|
|
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
2016-06-14 16:26:57 -07:00
|
|
|
var token: CssToken;
|
2016-02-02 10:37:08 +02:00
|
|
|
if (isPresent(previous) && previous.type == CssTokenType.Identifier &&
|
2016-06-21 11:10:25 -07:00
|
|
|
this._scanner.peek == chars.$LPAREN) {
|
2016-02-02 10:37:08 +02:00
|
|
|
token = this._consume(CssTokenType.Character, '(');
|
|
|
|
tokens.push(token);
|
|
|
|
|
|
|
|
this._scanner.setMode(CssLexerMode.STYLE_VALUE_FUNCTION);
|
|
|
|
|
|
|
|
token = this._scan();
|
|
|
|
tokens.push(token);
|
|
|
|
|
|
|
|
this._scanner.setMode(CssLexerMode.STYLE_VALUE);
|
|
|
|
|
|
|
|
token = this._consume(CssTokenType.Character, ')');
|
|
|
|
tokens.push(token);
|
|
|
|
} else {
|
|
|
|
token = this._scan();
|
2016-06-14 16:26:57 -07:00
|
|
|
if (token.type == CssTokenType.Whitespace) {
|
|
|
|
wsStr += token.strValue;
|
|
|
|
} else {
|
|
|
|
wsStr = '';
|
2016-02-02 10:37:08 +02:00
|
|
|
tokens.push(token);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
previous = token;
|
|
|
|
}
|
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
const end = this._getScannerIndex() - 1;
|
2016-02-02 10:37:08 +02:00
|
|
|
this._scanner.consumeWhitespace();
|
|
|
|
|
|
|
|
var code = this._scanner.peek;
|
2016-06-21 11:10:25 -07:00
|
|
|
if (code == chars.$SEMICOLON) {
|
2016-02-02 10:37:08 +02:00
|
|
|
this._consume(CssTokenType.Character, ';');
|
2016-06-21 11:10:25 -07:00
|
|
|
} else if (code != chars.$RBRACE) {
|
2016-02-02 10:37:08 +02:00
|
|
|
this._error(
|
2016-06-08 16:38:52 -07:00
|
|
|
generateErrorMessage(
|
|
|
|
this._scanner.input, `The CSS key/value definition did not end with a semicolon`,
|
|
|
|
previous.strValue, previous.index, previous.line, previous.column),
|
2016-02-02 10:37:08 +02:00
|
|
|
previous);
|
|
|
|
}
|
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
var strValue = this._scanner.input.substring(start, end);
|
2016-06-16 11:20:27 -07:00
|
|
|
return new CssStyleValueAst(start, end, tokens, strValue);
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-02-02 10:37:08 +02:00
|
|
|
_collectUntilDelim(delimiters: number, assertType: CssTokenType = null): CssToken[] {
|
2016-06-08 15:45:15 -07:00
|
|
|
var tokens: any[] /** TODO #9100 */ = [];
|
2016-02-02 10:37:08 +02:00
|
|
|
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
|
|
|
var val = isPresent(assertType) ? this._consume(assertType) : this._scan();
|
|
|
|
tokens.push(val);
|
|
|
|
}
|
|
|
|
return tokens;
|
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-06-16 11:20:27 -07:00
|
|
|
_parseBlock(delimiters: number): CssBlockAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
const start = this._getScannerIndex();
|
|
|
|
|
2016-06-16 11:54:20 -07:00
|
|
|
delimiters |= RBRACE_DELIM_FLAG;
|
2016-02-02 10:37:08 +02:00
|
|
|
|
|
|
|
this._scanner.setMode(CssLexerMode.BLOCK);
|
|
|
|
|
|
|
|
this._consume(CssTokenType.Character, '{');
|
|
|
|
this._scanner.consumeEmptyStatements();
|
|
|
|
|
2016-06-08 15:45:15 -07:00
|
|
|
var results: any[] /** TODO #9100 */ = [];
|
2016-02-02 10:37:08 +02:00
|
|
|
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
|
|
|
results.push(this._parseRule(delimiters));
|
|
|
|
}
|
|
|
|
|
|
|
|
this._consume(CssTokenType.Character, '}');
|
|
|
|
|
|
|
|
this._scanner.setMode(CssLexerMode.BLOCK);
|
|
|
|
this._scanner.consumeEmptyStatements();
|
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
const end = this._getScannerIndex() - 1;
|
2016-06-16 11:20:27 -07:00
|
|
|
return new CssBlockAst(start, end, results);
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-06-16 11:20:27 -07:00
|
|
|
_parseStyleBlock(delimiters: number): CssStylesBlockAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
const start = this._getScannerIndex();
|
|
|
|
|
2016-06-16 11:54:20 -07:00
|
|
|
delimiters |= RBRACE_DELIM_FLAG | LBRACE_DELIM_FLAG;
|
2016-02-02 10:37:08 +02:00
|
|
|
|
|
|
|
this._scanner.setMode(CssLexerMode.STYLE_BLOCK);
|
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
var result = this._consume(CssTokenType.Character, '{');
|
2016-06-21 11:10:25 -07:00
|
|
|
if (result.numValue != chars.$LBRACE) {
|
2016-06-14 16:26:57 -07:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
var definitions: CssDefinitionAst[] = [];
|
2016-02-02 10:37:08 +02:00
|
|
|
this._scanner.consumeEmptyStatements();
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
const end = this._getScannerIndex() - 1;
|
2016-06-16 11:20:27 -07:00
|
|
|
return new CssStylesBlockAst(start, end, definitions);
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-06-16 11:20:27 -07:00
|
|
|
_parseDefinition(delimiters: number): CssDefinitionAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
const start = this._getScannerIndex();
|
2016-02-02 10:37:08 +02:00
|
|
|
this._scanner.setMode(CssLexerMode.STYLE_BLOCK);
|
|
|
|
|
|
|
|
var prop = this._consume(CssTokenType.Identifier);
|
2016-06-08 15:45:15 -07:00
|
|
|
var parseValue: any /** TODO #9100 */, value: any /** TODO #9100 */ = null;
|
2016-02-02 10:37:08 +02:00
|
|
|
|
|
|
|
// 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) {
|
2016-06-21 11:10:25 -07:00
|
|
|
case chars.$COLON:
|
2016-02-02 10:37:08 +02:00
|
|
|
this._consume(CssTokenType.Character, ':');
|
|
|
|
parseValue = true;
|
|
|
|
break;
|
|
|
|
|
2016-06-21 11:10:25 -07:00
|
|
|
case chars.$SEMICOLON:
|
|
|
|
case chars.$RBRACE:
|
|
|
|
case chars.$EOF:
|
2016-02-02 10:37:08 +02:00
|
|
|
parseValue = false;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
var propStr = [prop.strValue];
|
2016-06-21 11:10:25 -07:00
|
|
|
if (this._scanner.peek != chars.$COLON) {
|
2016-02-02 10:37:08 +02:00
|
|
|
// this will throw the error
|
|
|
|
var nextValue = this._consume(CssTokenType.Character, ':');
|
|
|
|
propStr.push(nextValue.strValue);
|
|
|
|
|
|
|
|
var remainingTokens = this._collectUntilDelim(
|
2016-06-16 11:54:20 -07:00
|
|
|
delimiters | COLON_DELIM_FLAG | SEMICOLON_DELIM_FLAG, CssTokenType.Identifier);
|
2016-02-02 10:37:08 +02:00
|
|
|
if (remainingTokens.length > 0) {
|
|
|
|
remainingTokens.forEach((token) => { propStr.push(token.strValue); });
|
|
|
|
}
|
|
|
|
|
2016-06-08 16:38:52 -07:00
|
|
|
prop = new CssToken(prop.index, prop.column, prop.line, prop.type, propStr.join(' '));
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// this means we've reached the end of the definition and/or block
|
2016-06-21 11:10:25 -07:00
|
|
|
if (this._scanner.peek == chars.$COLON) {
|
2016-02-02 10:37:08 +02:00
|
|
|
this._consume(CssTokenType.Character, ':');
|
|
|
|
parseValue = true;
|
|
|
|
} else {
|
|
|
|
parseValue = false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parseValue) {
|
|
|
|
value = this._parseValue(delimiters);
|
|
|
|
} else {
|
2016-06-08 16:38:52 -07:00
|
|
|
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);
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-06-14 16:26:57 -07:00
|
|
|
const end = this._getScannerIndex() - 1;
|
2016-06-16 11:20:27 -07:00
|
|
|
return new CssDefinitionAst(start, end, prop, value);
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-02-02 10:37:08 +02:00
|
|
|
_assertCondition(status: boolean, errorMessage: string, problemToken: CssToken): boolean {
|
|
|
|
if (!status) {
|
|
|
|
this._error(errorMessage, problemToken);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-03-28 14:25:22 -07:00
|
|
|
/** @internal */
|
2016-02-02 10:37:08 +02:00
|
|
|
_error(message: string, problemToken: CssToken) {
|
|
|
|
var length = problemToken.strValue.length;
|
2016-06-08 16:38:52 -07:00
|
|
|
var error = CssParseError.create(
|
|
|
|
this._file, 0, problemToken.line, problemToken.column, length, message);
|
2016-02-02 10:37:08 +02:00
|
|
|
this._errors.push(error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssStyleValueAst extends CssAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
constructor(start: number, end: number, public tokens: CssToken[], public strValue: string) {
|
|
|
|
super(start, end);
|
|
|
|
}
|
2016-06-16 11:20:27 -07:00
|
|
|
visit(visitor: CssAstVisitor, context?: any): any { return visitor.visitCssValue(this); }
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export abstract class CssRuleAst extends CssAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
constructor(start: number, end: number) { super(start, end); }
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssBlockRuleAst extends CssRuleAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
constructor(
|
2016-06-16 11:20:27 -07:00
|
|
|
start: number, end: number, public type: BlockType, public block: CssBlockAst,
|
2016-06-14 16:26:57 -07:00
|
|
|
public name: CssToken = null) {
|
|
|
|
super(start, end);
|
|
|
|
}
|
2016-06-16 11:20:27 -07:00
|
|
|
visit(visitor: CssAstVisitor, context?: any): any {
|
2016-06-14 16:26:57 -07:00
|
|
|
return visitor.visitCssBlock(this.block, context);
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssKeyframeRuleAst extends CssBlockRuleAst {
|
|
|
|
constructor(start: number, end: number, name: CssToken, block: CssBlockAst) {
|
2016-06-14 16:26:57 -07:00
|
|
|
super(start, end, BlockType.Keyframes, block, name);
|
|
|
|
}
|
2016-06-16 11:20:27 -07:00
|
|
|
visit(visitor: CssAstVisitor, context?: any): any {
|
2016-06-14 16:26:57 -07:00
|
|
|
return visitor.visitCssKeyframeRule(this, context);
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssKeyframeDefinitionAst extends CssBlockRuleAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
public steps: CssToken[];
|
2016-06-16 11:20:27 -07:00
|
|
|
constructor(start: number, end: number, _steps: CssToken[], block: CssBlockAst) {
|
2016-06-14 16:26:57 -07:00
|
|
|
super(start, end, BlockType.Keyframes, block, mergeTokens(_steps, ','));
|
2016-02-02 10:37:08 +02:00
|
|
|
this.steps = _steps;
|
|
|
|
}
|
2016-06-16 11:20:27 -07:00
|
|
|
visit(visitor: CssAstVisitor, context?: any): any {
|
2016-06-14 16:26:57 -07:00
|
|
|
return visitor.visitCssKeyframeDefinition(this, context);
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssBlockDefinitionRuleAst extends CssBlockRuleAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
constructor(
|
|
|
|
start: number, end: number, public strValue: string, type: BlockType,
|
2016-06-16 11:20:27 -07:00
|
|
|
public query: CssAtRulePredicateAst, block: CssBlockAst) {
|
2016-06-14 16:26:57 -07:00
|
|
|
super(start, end, type, block);
|
|
|
|
var firstCssToken: CssToken = query.tokens[0];
|
2016-06-08 16:38:52 -07:00
|
|
|
this.name = new CssToken(
|
|
|
|
firstCssToken.index, firstCssToken.column, firstCssToken.line, CssTokenType.Identifier,
|
|
|
|
this.strValue);
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
2016-06-16 11:20:27 -07:00
|
|
|
visit(visitor: CssAstVisitor, context?: any): any {
|
2016-06-14 16:26:57 -07:00
|
|
|
return visitor.visitCssBlock(this.block, context);
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssMediaQueryRuleAst extends CssBlockDefinitionRuleAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
constructor(
|
2016-06-16 11:20:27 -07:00
|
|
|
start: number, end: number, strValue: string, query: CssAtRulePredicateAst,
|
|
|
|
block: CssBlockAst) {
|
2016-06-14 16:26:57 -07:00
|
|
|
super(start, end, strValue, BlockType.MediaQuery, query, block);
|
|
|
|
}
|
2016-06-16 11:20:27 -07:00
|
|
|
visit(visitor: CssAstVisitor, context?: any): any {
|
2016-06-14 16:26:57 -07:00
|
|
|
return visitor.visitCssMediaQueryRule(this, context);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssAtRulePredicateAst extends CssAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
constructor(start: number, end: number, public strValue: string, public tokens: CssToken[]) {
|
|
|
|
super(start, end);
|
|
|
|
}
|
2016-06-16 11:20:27 -07:00
|
|
|
visit(visitor: CssAstVisitor, context?: any): any {
|
2016-06-14 16:26:57 -07:00
|
|
|
return visitor.visitCssAtRulePredicate(this, context);
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssInlineRuleAst extends CssRuleAst {
|
|
|
|
constructor(start: number, end: number, public type: BlockType, public value: CssStyleValueAst) {
|
2016-06-14 16:26:57 -07:00
|
|
|
super(start, end);
|
|
|
|
}
|
2016-06-16 11:20:27 -07:00
|
|
|
visit(visitor: CssAstVisitor, context?: any): any {
|
2016-06-14 16:26:57 -07:00
|
|
|
return visitor.visitCssInlineRule(this, context);
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssSelectorRuleAst extends CssBlockRuleAst {
|
2016-02-02 10:37:08 +02:00
|
|
|
public strValue: string;
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
constructor(start: number, end: number, public selectors: CssSelectorAst[], block: CssBlockAst) {
|
2016-06-14 16:26:57 -07:00
|
|
|
super(start, end, BlockType.Selector, block);
|
2016-06-08 16:38:52 -07:00
|
|
|
this.strValue = selectors.map(selector => selector.strValue).join(',');
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
2016-06-16 11:20:27 -07:00
|
|
|
visit(visitor: CssAstVisitor, context?: any): any {
|
2016-06-14 16:26:57 -07:00
|
|
|
return visitor.visitCssSelectorRule(this, context);
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssDefinitionAst extends CssAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
constructor(
|
2016-06-16 11:20:27 -07:00
|
|
|
start: number, end: number, public property: CssToken, public value: CssStyleValueAst) {
|
2016-06-14 16:26:57 -07:00
|
|
|
super(start, end);
|
|
|
|
}
|
2016-06-16 11:20:27 -07:00
|
|
|
visit(visitor: CssAstVisitor, context?: any): any {
|
2016-06-14 16:26:57 -07:00
|
|
|
return visitor.visitCssDefinition(this, context);
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export abstract class CssSelectorPartAst extends CssAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
constructor(start: number, end: number) { super(start, end); }
|
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssSelectorAst extends CssSelectorPartAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
public strValue: string;
|
2016-06-16 11:20:27 -07:00
|
|
|
constructor(start: number, end: number, public selectorParts: CssSimpleSelectorAst[]) {
|
2016-06-14 16:26:57 -07:00
|
|
|
super(start, end);
|
|
|
|
this.strValue = selectorParts.map(part => part.strValue).join('');
|
|
|
|
}
|
2016-06-16 11:20:27 -07:00
|
|
|
visit(visitor: CssAstVisitor, context?: any): any {
|
2016-06-14 16:26:57 -07:00
|
|
|
return visitor.visitCssSelector(this, context);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssSimpleSelectorAst extends CssSelectorPartAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
constructor(
|
|
|
|
start: number, end: number, public tokens: CssToken[], public strValue: string,
|
2016-06-16 11:20:27 -07:00
|
|
|
public pseudoSelectors: CssPseudoSelectorAst[], public operator: CssToken) {
|
2016-06-14 16:26:57 -07:00
|
|
|
super(start, end);
|
|
|
|
}
|
2016-06-16 11:20:27 -07:00
|
|
|
visit(visitor: CssAstVisitor, context?: any): any {
|
2016-06-14 16:26:57 -07:00
|
|
|
return visitor.visitCssSimpleSelector(this, context);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssPseudoSelectorAst extends CssSelectorPartAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
constructor(
|
|
|
|
start: number, end: number, public strValue: string, public name: string,
|
2016-06-16 11:20:27 -07:00
|
|
|
public tokens: CssToken[], public innerSelectors: CssSelectorAst[]) {
|
2016-06-14 16:26:57 -07:00
|
|
|
super(start, end);
|
|
|
|
}
|
2016-06-16 11:20:27 -07:00
|
|
|
visit(visitor: CssAstVisitor, context?: any): any {
|
2016-06-14 16:26:57 -07:00
|
|
|
return visitor.visitCssPseudoSelector(this, context);
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
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); }
|
2016-06-14 16:26:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
a style block is different from a standard block because it contains
|
2016-06-16 11:20:27 -07:00
|
|
|
css prop:value definitions. A regular block can contain a list of Ast entries.
|
2016-06-14 16:26:57 -07:00
|
|
|
*/
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssStylesBlockAst extends CssBlockAst {
|
|
|
|
constructor(start: number, end: number, public definitions: CssDefinitionAst[]) {
|
2016-06-14 16:26:57 -07:00
|
|
|
super(start, end, definitions);
|
|
|
|
}
|
2016-06-16 11:20:27 -07:00
|
|
|
visit(visitor: CssAstVisitor, context?: any): any {
|
2016-06-14 16:26:57 -07:00
|
|
|
return visitor.visitCssStylesBlock(this, context);
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssStyleSheetAst extends CssAst {
|
|
|
|
constructor(start: number, end: number, public rules: CssAst[]) { super(start, end); }
|
|
|
|
visit(visitor: CssAstVisitor, context?: any): any {
|
2016-06-14 16:26:57 -07:00
|
|
|
return visitor.visitCssStyleSheet(this, context);
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export class CssParseError extends ParseError {
|
2016-06-08 16:38:52 -07:00
|
|
|
static create(
|
|
|
|
file: ParseSourceFile, offset: number, line: number, col: number, length: number,
|
|
|
|
errMsg: string): CssParseError {
|
2016-02-02 10:37:08 +02:00
|
|
|
var start = new ParseLocation(file, offset, line, col);
|
2016-06-14 16:26:57 -07:00
|
|
|
const end = new ParseLocation(file, offset, line, col + length);
|
2016-02-02 10:37:08 +02:00
|
|
|
var span = new ParseSourceSpan(start, end);
|
2016-06-08 16:38:52 -07:00
|
|
|
return new CssParseError(span, 'CSS Parse Error: ' + errMsg);
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
constructor(span: ParseSourceSpan, message: string) { super(span, message); }
|
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssUnknownRuleAst extends CssRuleAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
constructor(start: number, end: number, public ruleName: string, public tokens: CssToken[]) {
|
|
|
|
super(start, end);
|
|
|
|
}
|
2016-06-16 11:20:27 -07:00
|
|
|
visit(visitor: CssAstVisitor, context?: any): any {
|
2016-06-14 16:26:57 -07:00
|
|
|
return visitor.visitCssUnknownRule(this, context);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-16 11:20:27 -07:00
|
|
|
export class CssUnknownTokenListAst extends CssRuleAst {
|
2016-06-14 16:26:57 -07:00
|
|
|
constructor(start: number, end: number, public name: string, public tokens: CssToken[]) {
|
|
|
|
super(start, end);
|
|
|
|
}
|
2016-06-16 11:20:27 -07:00
|
|
|
visit(visitor: CssAstVisitor, context?: any): any {
|
2016-06-14 16:26:57 -07:00
|
|
|
return visitor.visitCssUnknownTokenList(this, context);
|
|
|
|
}
|
2016-02-02 10:37:08 +02:00
|
|
|
}
|