UNPKG

@angular/compiler

Version:

Angular - the compiler library

1,034 lines (1,033 loc) • 166 kB
/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ (function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define("@angular/compiler/src/expression_parser/parser", ["require", "exports", "tslib", "@angular/compiler/src/chars", "@angular/compiler/src/ml_parser/interpolation_config", "@angular/compiler/src/util", "@angular/compiler/src/expression_parser/ast", "@angular/compiler/src/expression_parser/lexer"], factory); } })(function (require, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports._ParseAST = exports.IvyParser = exports.Parser = exports.TemplateBindingParseResult = exports.SplitInterpolation = void 0; var tslib_1 = require("tslib"); var chars = require("@angular/compiler/src/chars"); var interpolation_config_1 = require("@angular/compiler/src/ml_parser/interpolation_config"); var util_1 = require("@angular/compiler/src/util"); var ast_1 = require("@angular/compiler/src/expression_parser/ast"); var lexer_1 = require("@angular/compiler/src/expression_parser/lexer"); var SplitInterpolation = /** @class */ (function () { function SplitInterpolation(strings, stringSpans, expressions, expressionsSpans, offsets) { this.strings = strings; this.stringSpans = stringSpans; this.expressions = expressions; this.expressionsSpans = expressionsSpans; this.offsets = offsets; } return SplitInterpolation; }()); exports.SplitInterpolation = SplitInterpolation; var TemplateBindingParseResult = /** @class */ (function () { function TemplateBindingParseResult(templateBindings, warnings, errors) { this.templateBindings = templateBindings; this.warnings = warnings; this.errors = errors; } return TemplateBindingParseResult; }()); exports.TemplateBindingParseResult = TemplateBindingParseResult; var defaultInterpolateRegExp = _createInterpolateRegExp(interpolation_config_1.DEFAULT_INTERPOLATION_CONFIG); function _getInterpolateRegExp(config) { if (config === interpolation_config_1.DEFAULT_INTERPOLATION_CONFIG) { return defaultInterpolateRegExp; } else { return _createInterpolateRegExp(config); } } function _createInterpolateRegExp(config) { var pattern = util_1.escapeRegExp(config.start) + '([\\s\\S]*?)' + util_1.escapeRegExp(config.end); return new RegExp(pattern, 'g'); } var Parser = /** @class */ (function () { function Parser(_lexer) { this._lexer = _lexer; this.errors = []; this.simpleExpressionChecker = SimpleExpressionChecker; } Parser.prototype.parseAction = function (input, location, absoluteOffset, interpolationConfig) { if (interpolationConfig === void 0) { interpolationConfig = interpolation_config_1.DEFAULT_INTERPOLATION_CONFIG; } this._checkNoInterpolation(input, location, interpolationConfig); var sourceToLex = this._stripComments(input); var tokens = this._lexer.tokenize(this._stripComments(input)); var ast = new _ParseAST(input, location, absoluteOffset, tokens, sourceToLex.length, true, this.errors, input.length - sourceToLex.length) .parseChain(); return new ast_1.ASTWithSource(ast, input, location, absoluteOffset, this.errors); }; Parser.prototype.parseBinding = function (input, location, absoluteOffset, interpolationConfig) { if (interpolationConfig === void 0) { interpolationConfig = interpolation_config_1.DEFAULT_INTERPOLATION_CONFIG; } var ast = this._parseBindingAst(input, location, absoluteOffset, interpolationConfig); return new ast_1.ASTWithSource(ast, input, location, absoluteOffset, this.errors); }; Parser.prototype.checkSimpleExpression = function (ast) { var checker = new this.simpleExpressionChecker(); ast.visit(checker); return checker.errors; }; Parser.prototype.parseSimpleBinding = function (input, location, absoluteOffset, interpolationConfig) { if (interpolationConfig === void 0) { interpolationConfig = interpolation_config_1.DEFAULT_INTERPOLATION_CONFIG; } var ast = this._parseBindingAst(input, location, absoluteOffset, interpolationConfig); var errors = this.checkSimpleExpression(ast); if (errors.length > 0) { this._reportError("Host binding expression cannot contain " + errors.join(' '), input, location); } return new ast_1.ASTWithSource(ast, input, location, absoluteOffset, this.errors); }; Parser.prototype._reportError = function (message, input, errLocation, ctxLocation) { this.errors.push(new ast_1.ParserError(message, input, errLocation, ctxLocation)); }; Parser.prototype._parseBindingAst = function (input, location, absoluteOffset, interpolationConfig) { // Quotes expressions use 3rd-party expression language. We don't want to use // our lexer or parser for that, so we check for that ahead of time. var quote = this._parseQuote(input, location, absoluteOffset); if (quote != null) { return quote; } this._checkNoInterpolation(input, location, interpolationConfig); var sourceToLex = this._stripComments(input); var tokens = this._lexer.tokenize(sourceToLex); return new _ParseAST(input, location, absoluteOffset, tokens, sourceToLex.length, false, this.errors, input.length - sourceToLex.length) .parseChain(); }; Parser.prototype._parseQuote = function (input, location, absoluteOffset) { if (input == null) return null; var prefixSeparatorIndex = input.indexOf(':'); if (prefixSeparatorIndex == -1) return null; var prefix = input.substring(0, prefixSeparatorIndex).trim(); if (!lexer_1.isIdentifier(prefix)) return null; var uninterpretedExpression = input.substring(prefixSeparatorIndex + 1); var span = new ast_1.ParseSpan(0, input.length); return new ast_1.Quote(span, span.toAbsolute(absoluteOffset), prefix, uninterpretedExpression, location); }; /** * Parse microsyntax template expression and return a list of bindings or * parsing errors in case the given expression is invalid. * * For example, * ``` * <div *ngFor="let item of items"> * ^ ^ absoluteValueOffset for `templateValue` * absoluteKeyOffset for `templateKey` * ``` * contains three bindings: * 1. ngFor -> null * 2. item -> NgForOfContext.$implicit * 3. ngForOf -> items * * This is apparent from the de-sugared template: * ``` * <ng-template ngFor let-item [ngForOf]="items"> * ``` * * @param templateKey name of directive, without the * prefix. For example: ngIf, ngFor * @param templateValue RHS of the microsyntax attribute * @param templateUrl template filename if it's external, component filename if it's inline * @param absoluteKeyOffset start of the `templateKey` * @param absoluteValueOffset start of the `templateValue` */ Parser.prototype.parseTemplateBindings = function (templateKey, templateValue, templateUrl, absoluteKeyOffset, absoluteValueOffset) { var tokens = this._lexer.tokenize(templateValue); var parser = new _ParseAST(templateValue, templateUrl, absoluteValueOffset, tokens, templateValue.length, false /* parseAction */, this.errors, 0 /* relative offset */); return parser.parseTemplateBindings({ source: templateKey, span: new ast_1.AbsoluteSourceSpan(absoluteKeyOffset, absoluteKeyOffset + templateKey.length), }); }; Parser.prototype.parseInterpolation = function (input, location, absoluteOffset, interpolationConfig) { if (interpolationConfig === void 0) { interpolationConfig = interpolation_config_1.DEFAULT_INTERPOLATION_CONFIG; } var split = this.splitInterpolation(input, location, interpolationConfig); if (split == null) return null; var expressions = []; for (var i = 0; i < split.expressions.length; ++i) { var expressionText = split.expressions[i]; var sourceToLex = this._stripComments(expressionText); var tokens = this._lexer.tokenize(sourceToLex); var ast = new _ParseAST(input, location, absoluteOffset, tokens, sourceToLex.length, false, this.errors, split.offsets[i] + (expressionText.length - sourceToLex.length)) .parseChain(); expressions.push(ast); } return this.createInterpolationAst(split.strings, expressions, input, location, absoluteOffset); }; /** * Similar to `parseInterpolation`, but treats the provided string as a single expression * element that would normally appear within the interpolation prefix and suffix (`{{` and `}}`). * This is used for parsing the switch expression in ICUs. */ Parser.prototype.parseInterpolationExpression = function (expression, location, absoluteOffset) { var sourceToLex = this._stripComments(expression); var tokens = this._lexer.tokenize(sourceToLex); var ast = new _ParseAST(expression, location, absoluteOffset, tokens, sourceToLex.length, /* parseAction */ false, this.errors, 0) .parseChain(); var strings = ['', '']; // The prefix and suffix strings are both empty return this.createInterpolationAst(strings, [ast], expression, location, absoluteOffset); }; Parser.prototype.createInterpolationAst = function (strings, expressions, input, location, absoluteOffset) { var span = new ast_1.ParseSpan(0, input.length); var interpolation = new ast_1.Interpolation(span, span.toAbsolute(absoluteOffset), strings, expressions); return new ast_1.ASTWithSource(interpolation, input, location, absoluteOffset, this.errors); }; /** * Splits a string of text into "raw" text segments and expressions present in interpolations in * the string. * Returns `null` if there are no interpolations, otherwise a * `SplitInterpolation` with splits that look like * <raw text> <expression> <raw text> ... <raw text> <expression> <raw text> */ Parser.prototype.splitInterpolation = function (input, location, interpolationConfig) { if (interpolationConfig === void 0) { interpolationConfig = interpolation_config_1.DEFAULT_INTERPOLATION_CONFIG; } var strings = []; var expressions = []; var offsets = []; var stringSpans = []; var expressionSpans = []; var i = 0; var atInterpolation = false; var extendLastString = false; var interpStart = interpolationConfig.start, interpEnd = interpolationConfig.end; while (i < input.length) { if (!atInterpolation) { // parse until starting {{ var start = i; i = input.indexOf(interpStart, i); if (i === -1) { i = input.length; } var part = input.substring(start, i); strings.push(part); stringSpans.push({ start: start, end: i }); atInterpolation = true; } else { // parse from starting {{ to ending }} var fullStart = i; var exprStart = fullStart + interpStart.length; var exprEnd = input.indexOf(interpEnd, exprStart); if (exprEnd === -1) { // Could not find the end of the interpolation; do not parse an expression. // Instead we should extend the content on the last raw string. atInterpolation = false; extendLastString = true; break; } var fullEnd = exprEnd + interpEnd.length; var part = input.substring(exprStart, exprEnd); if (part.trim().length > 0) { expressions.push(part); } else { this._reportError('Blank expressions are not allowed in interpolated strings', input, "at column " + i + " in", location); expressions.push('$implicit'); } offsets.push(exprStart); expressionSpans.push({ start: fullStart, end: fullEnd }); i = fullEnd; atInterpolation = false; } } if (!atInterpolation) { // If we are now at a text section, add the remaining content as a raw string. if (extendLastString) { strings[strings.length - 1] += input.substring(i); stringSpans[stringSpans.length - 1].end = input.length; } else { strings.push(input.substring(i)); stringSpans.push({ start: i, end: input.length }); } } return expressions.length === 0 ? null : new SplitInterpolation(strings, stringSpans, expressions, expressionSpans, offsets); }; Parser.prototype.wrapLiteralPrimitive = function (input, location, absoluteOffset) { var span = new ast_1.ParseSpan(0, input == null ? 0 : input.length); return new ast_1.ASTWithSource(new ast_1.LiteralPrimitive(span, span.toAbsolute(absoluteOffset), input), input, location, absoluteOffset, this.errors); }; Parser.prototype._stripComments = function (input) { var i = this._commentStart(input); return i != null ? input.substring(0, i).trim() : input; }; Parser.prototype._commentStart = function (input) { var outerQuote = null; for (var i = 0; i < input.length - 1; i++) { var char = input.charCodeAt(i); var nextChar = input.charCodeAt(i + 1); if (char === chars.$SLASH && nextChar == chars.$SLASH && outerQuote == null) return i; if (outerQuote === char) { outerQuote = null; } else if (outerQuote == null && lexer_1.isQuote(char)) { outerQuote = char; } } return null; }; Parser.prototype._checkNoInterpolation = function (input, location, interpolationConfig) { var regexp = _getInterpolateRegExp(interpolationConfig); var parts = input.split(regexp); if (parts.length > 1) { this._reportError("Got interpolation (" + interpolationConfig.start + interpolationConfig.end + ") where expression was expected", input, "at column " + this._findInterpolationErrorColumn(parts, 1, interpolationConfig) + " in", location); } }; Parser.prototype._findInterpolationErrorColumn = function (parts, partInErrIdx, interpolationConfig) { var errLocation = ''; for (var j = 0; j < partInErrIdx; j++) { errLocation += j % 2 === 0 ? parts[j] : "" + interpolationConfig.start + parts[j] + interpolationConfig.end; } return errLocation.length; }; return Parser; }()); exports.Parser = Parser; var IvyParser = /** @class */ (function (_super) { tslib_1.__extends(IvyParser, _super); function IvyParser() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.simpleExpressionChecker = IvySimpleExpressionChecker; // return _this; } return IvyParser; }(Parser)); exports.IvyParser = IvyParser; /** Describes a stateful context an expression parser is in. */ var ParseContextFlags; (function (ParseContextFlags) { ParseContextFlags[ParseContextFlags["None"] = 0] = "None"; /** * A Writable context is one in which a value may be written to an lvalue. * For example, after we see a property access, we may expect a write to the * property via the "=" operator. * prop * ^ possible "=" after */ ParseContextFlags[ParseContextFlags["Writable"] = 1] = "Writable"; })(ParseContextFlags || (ParseContextFlags = {})); var _ParseAST = /** @class */ (function () { function _ParseAST(input, location, absoluteOffset, tokens, inputLength, parseAction, errors, offset) { this.input = input; this.location = location; this.absoluteOffset = absoluteOffset; this.tokens = tokens; this.inputLength = inputLength; this.parseAction = parseAction; this.errors = errors; this.offset = offset; this.rparensExpected = 0; this.rbracketsExpected = 0; this.rbracesExpected = 0; this.context = ParseContextFlags.None; // Cache of expression start and input indeces to the absolute source span they map to, used to // prevent creating superfluous source spans in `sourceSpan`. // A serial of the expression start and input index is used for mapping because both are stateful // and may change for subsequent expressions visited by the parser. this.sourceSpanCache = new Map(); this.index = 0; } _ParseAST.prototype.peek = function (offset) { var i = this.index + offset; return i < this.tokens.length ? this.tokens[i] : lexer_1.EOF; }; Object.defineProperty(_ParseAST.prototype, "next", { get: function () { return this.peek(0); }, enumerable: false, configurable: true }); Object.defineProperty(_ParseAST.prototype, "atEOF", { /** Whether all the parser input has been processed. */ get: function () { return this.index >= this.tokens.length; }, enumerable: false, configurable: true }); Object.defineProperty(_ParseAST.prototype, "inputIndex", { /** * Index of the next token to be processed, or the end of the last token if all have been * processed. */ get: function () { return this.atEOF ? this.currentEndIndex : this.next.index + this.offset; }, enumerable: false, configurable: true }); Object.defineProperty(_ParseAST.prototype, "currentEndIndex", { /** * End index of the last processed token, or the start of the first token if none have been * processed. */ get: function () { if (this.index > 0) { var curToken = this.peek(-1); return curToken.end + this.offset; } // No tokens have been processed yet; return the next token's start or the length of the input // if there is no token. if (this.tokens.length === 0) { return this.inputLength + this.offset; } return this.next.index + this.offset; }, enumerable: false, configurable: true }); Object.defineProperty(_ParseAST.prototype, "currentAbsoluteOffset", { /** * Returns the absolute offset of the start of the current token. */ get: function () { return this.absoluteOffset + this.inputIndex; }, enumerable: false, configurable: true }); _ParseAST.prototype.span = function (start) { return new ast_1.ParseSpan(start, this.currentEndIndex); }; _ParseAST.prototype.sourceSpan = function (start) { var serial = start + "@" + this.inputIndex; if (!this.sourceSpanCache.has(serial)) { this.sourceSpanCache.set(serial, this.span(start).toAbsolute(this.absoluteOffset)); } return this.sourceSpanCache.get(serial); }; _ParseAST.prototype.advance = function () { this.index++; }; /** * Executes a callback in the provided context. */ _ParseAST.prototype.withContext = function (context, cb) { this.context |= context; var ret = cb(); this.context ^= context; return ret; }; _ParseAST.prototype.consumeOptionalCharacter = function (code) { if (this.next.isCharacter(code)) { this.advance(); return true; } else { return false; } }; _ParseAST.prototype.peekKeywordLet = function () { return this.next.isKeywordLet(); }; _ParseAST.prototype.peekKeywordAs = function () { return this.next.isKeywordAs(); }; /** * Consumes an expected character, otherwise emits an error about the missing expected character * and skips over the token stream until reaching a recoverable point. * * See `this.error` and `this.skip` for more details. */ _ParseAST.prototype.expectCharacter = function (code) { if (this.consumeOptionalCharacter(code)) return; this.error("Missing expected " + String.fromCharCode(code)); }; _ParseAST.prototype.consumeOptionalOperator = function (op) { if (this.next.isOperator(op)) { this.advance(); return true; } else { return false; } }; _ParseAST.prototype.expectOperator = function (operator) { if (this.consumeOptionalOperator(operator)) return; this.error("Missing expected operator " + operator); }; _ParseAST.prototype.prettyPrintToken = function (tok) { return tok === lexer_1.EOF ? 'end of input' : "token " + tok; }; _ParseAST.prototype.expectIdentifierOrKeyword = function () { var n = this.next; if (!n.isIdentifier() && !n.isKeyword()) { this.error("Unexpected " + this.prettyPrintToken(n) + ", expected identifier or keyword"); return ''; } this.advance(); return n.toString(); }; _ParseAST.prototype.expectIdentifierOrKeywordOrString = function () { var n = this.next; if (!n.isIdentifier() && !n.isKeyword() && !n.isString()) { this.error("Unexpected " + this.prettyPrintToken(n) + ", expected identifier, keyword, or string"); return ''; } this.advance(); return n.toString(); }; _ParseAST.prototype.parseChain = function () { var exprs = []; var start = this.inputIndex; while (this.index < this.tokens.length) { var expr = this.parsePipe(); exprs.push(expr); if (this.consumeOptionalCharacter(chars.$SEMICOLON)) { if (!this.parseAction) { this.error('Binding expression cannot contain chained expression'); } while (this.consumeOptionalCharacter(chars.$SEMICOLON)) { } // read all semicolons } else if (this.index < this.tokens.length) { this.error("Unexpected token '" + this.next + "'"); } } if (exprs.length == 0) return new ast_1.EmptyExpr(this.span(start), this.sourceSpan(start)); if (exprs.length == 1) return exprs[0]; return new ast_1.Chain(this.span(start), this.sourceSpan(start), exprs); }; _ParseAST.prototype.parsePipe = function () { var result = this.parseExpression(); if (this.consumeOptionalOperator('|')) { if (this.parseAction) { this.error('Cannot have a pipe in an action expression'); } do { var nameStart = this.inputIndex; var name_1 = this.expectIdentifierOrKeyword(); var nameSpan = this.sourceSpan(nameStart); var args = []; while (this.consumeOptionalCharacter(chars.$COLON)) { args.push(this.parseExpression()); } var start = result.span.start; result = new ast_1.BindingPipe(this.span(start), this.sourceSpan(start), result, name_1, args, nameSpan); } while (this.consumeOptionalOperator('|')); } return result; }; _ParseAST.prototype.parseExpression = function () { return this.parseConditional(); }; _ParseAST.prototype.parseConditional = function () { var start = this.inputIndex; var result = this.parseLogicalOr(); if (this.consumeOptionalOperator('?')) { var yes = this.parsePipe(); var no = void 0; if (!this.consumeOptionalCharacter(chars.$COLON)) { var end = this.inputIndex; var expression = this.input.substring(start, end); this.error("Conditional expression " + expression + " requires all 3 expressions"); no = new ast_1.EmptyExpr(this.span(start), this.sourceSpan(start)); } else { no = this.parsePipe(); } return new ast_1.Conditional(this.span(start), this.sourceSpan(start), result, yes, no); } else { return result; } }; _ParseAST.prototype.parseLogicalOr = function () { // '||' var result = this.parseLogicalAnd(); while (this.consumeOptionalOperator('||')) { var right = this.parseLogicalAnd(); var start = result.span.start; result = new ast_1.Binary(this.span(start), this.sourceSpan(start), '||', result, right); } return result; }; _ParseAST.prototype.parseLogicalAnd = function () { // '&&' var result = this.parseEquality(); while (this.consumeOptionalOperator('&&')) { var right = this.parseEquality(); var start = result.span.start; result = new ast_1.Binary(this.span(start), this.sourceSpan(start), '&&', result, right); } return result; }; _ParseAST.prototype.parseEquality = function () { // '==','!=','===','!==' var result = this.parseRelational(); while (this.next.type == lexer_1.TokenType.Operator) { var operator = this.next.strValue; switch (operator) { case '==': case '===': case '!=': case '!==': this.advance(); var right = this.parseRelational(); var start = result.span.start; result = new ast_1.Binary(this.span(start), this.sourceSpan(start), operator, result, right); continue; } break; } return result; }; _ParseAST.prototype.parseRelational = function () { // '<', '>', '<=', '>=' var result = this.parseAdditive(); while (this.next.type == lexer_1.TokenType.Operator) { var operator = this.next.strValue; switch (operator) { case '<': case '>': case '<=': case '>=': this.advance(); var right = this.parseAdditive(); var start = result.span.start; result = new ast_1.Binary(this.span(start), this.sourceSpan(start), operator, result, right); continue; } break; } return result; }; _ParseAST.prototype.parseAdditive = function () { // '+', '-' var result = this.parseMultiplicative(); while (this.next.type == lexer_1.TokenType.Operator) { var operator = this.next.strValue; switch (operator) { case '+': case '-': this.advance(); var right = this.parseMultiplicative(); var start = result.span.start; result = new ast_1.Binary(this.span(start), this.sourceSpan(start), operator, result, right); continue; } break; } return result; }; _ParseAST.prototype.parseMultiplicative = function () { // '*', '%', '/' var result = this.parsePrefix(); while (this.next.type == lexer_1.TokenType.Operator) { var operator = this.next.strValue; switch (operator) { case '*': case '%': case '/': this.advance(); var right = this.parsePrefix(); var start = result.span.start; result = new ast_1.Binary(this.span(start), this.sourceSpan(start), operator, result, right); continue; } break; } return result; }; _ParseAST.prototype.parsePrefix = function () { if (this.next.type == lexer_1.TokenType.Operator) { var start = this.inputIndex; var operator = this.next.strValue; var result = void 0; switch (operator) { case '+': this.advance(); result = this.parsePrefix(); return ast_1.Unary.createPlus(this.span(start), this.sourceSpan(start), result); case '-': this.advance(); result = this.parsePrefix(); return ast_1.Unary.createMinus(this.span(start), this.sourceSpan(start), result); case '!': this.advance(); result = this.parsePrefix(); return new ast_1.PrefixNot(this.span(start), this.sourceSpan(start), result); } } return this.parseCallChain(); }; _ParseAST.prototype.parseCallChain = function () { var _this = this; var result = this.parsePrimary(); var resultStart = result.span.start; while (true) { if (this.consumeOptionalCharacter(chars.$PERIOD)) { result = this.parseAccessMemberOrMethodCall(result, false); } else if (this.consumeOptionalOperator('?.')) { result = this.parseAccessMemberOrMethodCall(result, true); } else if (this.consumeOptionalCharacter(chars.$LBRACKET)) { this.withContext(ParseContextFlags.Writable, function () { _this.rbracketsExpected++; var key = _this.parsePipe(); if (key instanceof ast_1.EmptyExpr) { _this.error("Key access cannot be empty"); } _this.rbracketsExpected--; _this.expectCharacter(chars.$RBRACKET); if (_this.consumeOptionalOperator('=')) { var value = _this.parseConditional(); result = new ast_1.KeyedWrite(_this.span(resultStart), _this.sourceSpan(resultStart), result, key, value); } else { result = new ast_1.KeyedRead(_this.span(resultStart), _this.sourceSpan(resultStart), result, key); } }); } else if (this.consumeOptionalCharacter(chars.$LPAREN)) { this.rparensExpected++; var args = this.parseCallArguments(); this.rparensExpected--; this.expectCharacter(chars.$RPAREN); result = new ast_1.FunctionCall(this.span(resultStart), this.sourceSpan(resultStart), result, args); } else if (this.consumeOptionalOperator('!')) { result = new ast_1.NonNullAssert(this.span(resultStart), this.sourceSpan(resultStart), result); } else { return result; } } }; _ParseAST.prototype.parsePrimary = function () { var start = this.inputIndex; if (this.consumeOptionalCharacter(chars.$LPAREN)) { this.rparensExpected++; var result = this.parsePipe(); this.rparensExpected--; this.expectCharacter(chars.$RPAREN); return result; } else if (this.next.isKeywordNull()) { this.advance(); return new ast_1.LiteralPrimitive(this.span(start), this.sourceSpan(start), null); } else if (this.next.isKeywordUndefined()) { this.advance(); return new ast_1.LiteralPrimitive(this.span(start), this.sourceSpan(start), void 0); } else if (this.next.isKeywordTrue()) { this.advance(); return new ast_1.LiteralPrimitive(this.span(start), this.sourceSpan(start), true); } else if (this.next.isKeywordFalse()) { this.advance(); return new ast_1.LiteralPrimitive(this.span(start), this.sourceSpan(start), false); } else if (this.next.isKeywordThis()) { this.advance(); return new ast_1.ThisReceiver(this.span(start), this.sourceSpan(start)); } else if (this.consumeOptionalCharacter(chars.$LBRACKET)) { this.rbracketsExpected++; var elements = this.parseExpressionList(chars.$RBRACKET); this.rbracketsExpected--; this.expectCharacter(chars.$RBRACKET); return new ast_1.LiteralArray(this.span(start), this.sourceSpan(start), elements); } else if (this.next.isCharacter(chars.$LBRACE)) { return this.parseLiteralMap(); } else if (this.next.isIdentifier()) { return this.parseAccessMemberOrMethodCall(new ast_1.ImplicitReceiver(this.span(start), this.sourceSpan(start)), false); } else if (this.next.isNumber()) { var value = this.next.toNumber(); this.advance(); return new ast_1.LiteralPrimitive(this.span(start), this.sourceSpan(start), value); } else if (this.next.isString()) { var literalValue = this.next.toString(); this.advance(); return new ast_1.LiteralPrimitive(this.span(start), this.sourceSpan(start), literalValue); } else if (this.index >= this.tokens.length) { this.error("Unexpected end of expression: " + this.input); return new ast_1.EmptyExpr(this.span(start), this.sourceSpan(start)); } else { this.error("Unexpected token " + this.next); return new ast_1.EmptyExpr(this.span(start), this.sourceSpan(start)); } }; _ParseAST.prototype.parseExpressionList = function (terminator) { var result = []; if (!this.next.isCharacter(terminator)) { do { result.push(this.parsePipe()); } while (this.consumeOptionalCharacter(chars.$COMMA)); } return result; }; _ParseAST.prototype.parseLiteralMap = function () { var keys = []; var values = []; var start = this.inputIndex; this.expectCharacter(chars.$LBRACE); if (!this.consumeOptionalCharacter(chars.$RBRACE)) { this.rbracesExpected++; do { var quoted = this.next.isString(); var key = this.expectIdentifierOrKeywordOrString(); keys.push({ key: key, quoted: quoted }); this.expectCharacter(chars.$COLON); values.push(this.parsePipe()); } while (this.consumeOptionalCharacter(chars.$COMMA)); this.rbracesExpected--; this.expectCharacter(chars.$RBRACE); } return new ast_1.LiteralMap(this.span(start), this.sourceSpan(start), keys, values); }; _ParseAST.prototype.parseAccessMemberOrMethodCall = function (receiver, isSafe) { if (isSafe === void 0) { isSafe = false; } var start = receiver.span.start; var nameStart = this.inputIndex; var id = this.expectIdentifierOrKeyword(); var nameSpan = this.sourceSpan(nameStart); if (this.consumeOptionalCharacter(chars.$LPAREN)) { this.rparensExpected++; var args = this.parseCallArguments(); this.expectCharacter(chars.$RPAREN); this.rparensExpected--; var span = this.span(start); var sourceSpan = this.sourceSpan(start); return isSafe ? new ast_1.SafeMethodCall(span, sourceSpan, nameSpan, receiver, id, args) : new ast_1.MethodCall(span, sourceSpan, nameSpan, receiver, id, args); } else { if (isSafe) { if (this.consumeOptionalOperator('=')) { this.error('The \'?.\' operator cannot be used in the assignment'); return new ast_1.EmptyExpr(this.span(start), this.sourceSpan(start)); } else { return new ast_1.SafePropertyRead(this.span(start), this.sourceSpan(start), nameSpan, receiver, id); } } else { if (this.consumeOptionalOperator('=')) { if (!this.parseAction) { this.error('Bindings cannot contain assignments'); return new ast_1.EmptyExpr(this.span(start), this.sourceSpan(start)); } var value = this.parseConditional(); return new ast_1.PropertyWrite(this.span(start), this.sourceSpan(start), nameSpan, receiver, id, value); } else { return new ast_1.PropertyRead(this.span(start), this.sourceSpan(start), nameSpan, receiver, id); } } } }; _ParseAST.prototype.parseCallArguments = function () { if (this.next.isCharacter(chars.$RPAREN)) return []; var positionals = []; do { positionals.push(this.parsePipe()); } while (this.consumeOptionalCharacter(chars.$COMMA)); return positionals; }; /** * Parses an identifier, a keyword, a string with an optional `-` in between, * and returns the string along with its absolute source span. */ _ParseAST.prototype.expectTemplateBindingKey = function () { var result = ''; var operatorFound = false; var start = this.currentAbsoluteOffset; do { result += this.expectIdentifierOrKeywordOrString(); operatorFound = this.consumeOptionalOperator('-'); if (operatorFound) { result += '-'; } } while (operatorFound); return { source: result, span: new ast_1.AbsoluteSourceSpan(start, start + result.length), }; }; /** * Parse microsyntax template expression and return a list of bindings or * parsing errors in case the given expression is invalid. * * For example, * ``` * <div *ngFor="let item of items; index as i; trackBy: func"> * ``` * contains five bindings: * 1. ngFor -> null * 2. item -> NgForOfContext.$implicit * 3. ngForOf -> items * 4. i -> NgForOfContext.index * 5. ngForTrackBy -> func * * For a full description of the microsyntax grammar, see * https://gist.github.com/mhevery/d3530294cff2e4a1b3fe15ff75d08855 * * @param templateKey name of the microsyntax directive, like ngIf, ngFor, * without the *, along with its absolute span. */ _ParseAST.prototype.parseTemplateBindings = function (templateKey) { var bindings = []; // The first binding is for the template key itself // In *ngFor="let item of items", key = "ngFor", value = null // In *ngIf="cond | pipe", key = "ngIf", value = "cond | pipe" bindings.push.apply(bindings, tslib_1.__spread(this.parseDirectiveKeywordBindings(templateKey))); while (this.index < this.tokens.length) { // If it starts with 'let', then this must be variable declaration var letBinding = this.parseLetBinding(); if (letBinding) { bindings.push(letBinding); } else { // Two possible cases here, either `value "as" key` or // "directive-keyword expression". We don't know which case, but both // "value" and "directive-keyword" are template binding key, so consume // the key first. var key = this.expectTemplateBindingKey(); // Peek at the next token, if it is "as" then this must be variable // declaration. var binding = this.parseAsBinding(key); if (binding) { bindings.push(binding); } else { // Otherwise the key must be a directive keyword, like "of". Transform // the key to actual key. Eg. of -> ngForOf, trackBy -> ngForTrackBy key.source = templateKey.source + key.source[0].toUpperCase() + key.source.substring(1); bindings.push.apply(bindings, tslib_1.__spread(this.parseDirectiveKeywordBindings(key))); } } this.consumeStatementTerminator(); } return new TemplateBindingParseResult(bindings, [] /* warnings */, this.errors); }; /** * Parse a directive keyword, followed by a mandatory expression. * For example, "of items", "trackBy: func". * The bindings are: ngForOf -> items, ngForTrackBy -> func * There could be an optional "as" binding that follows the expression. * For example, * ``` * *ngFor="let item of items | slice:0:1 as collection". * ^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ * keyword bound target optional 'as' binding * ``` * * @param key binding key, for example, ngFor, ngIf, ngForOf, along with its * absolute span. */ _ParseAST.prototype.parseDirectiveKeywordBindings = function (key) { var bindings = []; this.consumeOptionalCharacter(chars.$COLON); // trackBy: trackByFunction var value = this.getDirectiveBoundTarget(); var spanEnd = this.currentAbsoluteOffset; // The binding could optionally be followed by "as". For example, // *ngIf="cond | pipe as x". In this case, the key in the "as" binding // is "x" and the value is the template key itself ("ngIf"). Note that the // 'key' in the current context now becomes the "value" in the next binding. var asBinding = this.parseAsBinding(key); if (!asBinding) { this.consumeStatementTerminator(); spanEnd = this.currentAbsoluteOffset; } var sourceSpan = new ast_1.AbsoluteSourceSpan(key.span.start, spanEnd); bindings.push(new ast_1.ExpressionBinding(sourceSpan, key, value)); if (asBinding) { bindings.push(asBinding); } return bindings; }; /** * Return the expression AST for the bound target of a directive keyword * binding. For example, * ``` * *ngIf="condition | pipe" * ^^^^^^^^^^^^^^^^ bound target for "ngIf" * *ngFor="let item of items" * ^^^^^ bound target for "ngForOf" * ``` */ _ParseAST.prototype.getDirectiveBoundTarget = function () { if (this.next === lexer_1.EOF || this.peekKeywordAs() || this.peekKeywordLet()) { return null; } var ast = this.parsePipe(); // example: "condition | async" var _a = ast.span, start = _a.start, end = _a.end; var value = this.input.substring(start, end); return new ast_1.ASTWithSource(ast, value, this.location, this.absoluteOffset + start, this.errors); }; /** * Return the binding for a variable declared using `as`. Note that the order * of the key-value pair in this declaration is reversed. For example, * ``` * *ngFor="let item of items; index as i" * ^^^^^ ^ * value key * ``` * * @param value name of the value in the declaration, "ngIf" in the example * above, along with its absolute span. */ _ParseAST.prototype.parseAsBinding = function (value) { if (!this.peekKeywordAs()) { return null; } this.advance(); // consume the 'as' keyword var key = this.expectTemplateBindingKey(); this.consumeStatementTerminator(); var sourceSpan = new ast_1.AbsoluteSourceSpan(value.span.start, this.currentAbsoluteOffset); return new ast_1.VariableBinding(sourceSpan, key, value); }; /** * Return the binding for a variable declared using `let`. For example, * ``` * *ngFor="let item of items; let i=index;" * ^^^^^^^^ ^^^^^^^^^^^ * ``` * In the first binding, `item` is bound to `NgForOfContext.$implicit`. * In the second binding, `i` is bound to `NgForOfContext.index`. */ _ParseAST.prototype.parseLetBinding = function () { if (!this.peekKeywordLet()) { return null; } var spanStart = this.currentAbsoluteOffset; this.advance(); // consume the 'let' keyword