/
githubmirror
/
angular
Обзор
Документация
Войти
/
githubmirror
/
angular
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/compiler/src/expression_parser/ast.ts
952 строки
26 KB
Matthieu Riegler
feat(compiler): Add support for the `instanceof` binary operator
13 янв 2026, 19:33
13 янв 2026, 19:33
72534e2
Код
Авторство
О чём код?
/** * @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.dev/license */ import {SecurityContext} from '../core'; import {ParseError, ParseSourceSpan} from '../parse_util'; export class ParseSpan { constructor( public start: number, public end: number, ) {} toAbsolute(absoluteOffset: number): AbsoluteSourceSpan { return new AbsoluteSourceSpan(absoluteOffset + this.start, absoluteOffset + this.end); } } export abstract class AST { constructor( public span: ParseSpan, /** * Absolute location of the expression AST in a source code file. */ public sourceSpan: AbsoluteSourceSpan, ) {} abstract visit(visitor: AstVisitor, context?: any): any; toString(): string { return 'AST'; } } export abstract class ASTWithName extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public nameSpan: AbsoluteSourceSpan, ) { super(span, sourceSpan); } } export class EmptyExpr extends AST { override visit(visitor: AstVisitor, context: any = null) { return visitor.visitEmptyExpr?.(this, context); } } export class ImplicitReceiver extends AST { override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitImplicitReceiver(this, context); } } export class ThisReceiver extends AST { override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitThisReceiver?.(this, context); } } /** * Multiple expressions separated by a semicolon. */ export class Chain extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public expressions: AST[], ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitChain(this, context); } } export class Conditional extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public condition: AST, public trueExp: AST, public falseExp: AST, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitConditional(this, context); } } export class PropertyRead extends ASTWithName { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, nameSpan: AbsoluteSourceSpan, public receiver: AST, public name: string, ) { super(span, sourceSpan, nameSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitPropertyRead(this, context); } } export class SafePropertyRead extends ASTWithName { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, nameSpan: AbsoluteSourceSpan, public receiver: AST, public name: string, ) { super(span, sourceSpan, nameSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitSafePropertyRead(this, context); } } export class KeyedRead extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public receiver: AST, public key: AST, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitKeyedRead(this, context); } } export class SafeKeyedRead extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public receiver: AST, public key: AST, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitSafeKeyedRead(this, context); } } /** Possible types for a pipe. */ export enum BindingPipeType { /** * Pipe is being referenced by its name, for example: * `@Pipe({name: 'foo'}) class FooPipe` and `{{123 | foo}}`. */ ReferencedByName, /** * Pipe is being referenced by its class name, for example: * `@Pipe() class FooPipe` and `{{123 | FooPipe}}`. */ ReferencedDirectly, } export class BindingPipe extends ASTWithName { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public exp: AST, public name: string, public args: AST[], readonly type: BindingPipeType, nameSpan: AbsoluteSourceSpan, ) { super(span, sourceSpan, nameSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitPipe(this, context); } } export class LiteralPrimitive extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public value: string | number | boolean | null | undefined, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitLiteralPrimitive(this, context); } } export class LiteralArray extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public expressions: AST[], ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitLiteralArray(this, context); } } export class SpreadElement extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, readonly expression: AST, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitSpreadElement(this, context); } } export interface LiteralMapPropertyKey { kind: 'property'; key: string; quoted: boolean; span: ParseSpan; sourceSpan: AbsoluteSourceSpan; isShorthandInitialized?: boolean; } export interface LiteralMapSpreadKey { kind: 'spread'; span: ParseSpan; sourceSpan: AbsoluteSourceSpan; } export type LiteralMapKey = LiteralMapPropertyKey | LiteralMapSpreadKey; export class LiteralMap extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public keys: LiteralMapKey[], public values: AST[], ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitLiteralMap(this, context); } } export class Interpolation extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public strings: string[], public expressions: AST[], ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitInterpolation(this, context); } } export type AssignmentOperation = | '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '**=' | '&&=' | '||=' | '??='; type BinaryOperation = | AssignmentOperation // Logical | '&&' | '||' | '??' // Equality | '==' | '!=' | '===' | '!==' // Relational | '<' | '>' | '<=' | '>=' | 'in' | 'instanceof' // Additive | '+' | '-' // Multiplicative | '*' | '%' | '/' // Exponentiation | '**'; export class Binary extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public operation: BinaryOperation, public left: AST, public right: AST, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitBinary(this, context); } static isAssignmentOperation(op: string): op is AssignmentOperation { return ( op === '=' || op === '+=' || op === '-=' || op === '*=' || op === '/=' || op === '%=' || op === '**=' || op === '&&=' || op === '||=' || op === '??=' ); } } /** * For backwards compatibility reasons, `Unary` inherits from `Binary` and mimics the binary AST * node that was originally used. This inheritance relation can be deleted in some future major, * after consumers have been given a chance to fully support Unary. */ export class Unary extends Binary { // Redeclare the properties that are inherited from `Binary` as `never`, as consumers should not // depend on these fields when operating on `Unary`. override left: never = null as never; override right: never = null as never; override operation: never = null as never; /** * Creates a unary minus expression "-x", represented as `Binary` using "0 - x". */ static createMinus(span: ParseSpan, sourceSpan: AbsoluteSourceSpan, expr: AST): Unary { return new Unary( span, sourceSpan, '-', expr, '-', new LiteralPrimitive(span, sourceSpan, 0), expr, ); } /** * Creates a unary plus expression "+x", represented as `Binary` using "x - 0". */ static createPlus(span: ParseSpan, sourceSpan: AbsoluteSourceSpan, expr: AST): Unary { return new Unary( span, sourceSpan, '+', expr, '-', expr, new LiteralPrimitive(span, sourceSpan, 0), ); } /** * During the deprecation period this constructor is private, to avoid consumers from creating * a `Unary` with the fallback properties for `Binary`. */ private constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public operator: '+' | '-', public expr: AST, binaryOp: BinaryOperation, binaryLeft: AST, binaryRight: AST, ) { super(span, sourceSpan, binaryOp, binaryLeft, binaryRight); } override visit(visitor: AstVisitor, context: any = null): any { if (visitor.visitUnary !== undefined) { return visitor.visitUnary(this, context); } return visitor.visitBinary(this, context); } } export class PrefixNot extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public expression: AST, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitPrefixNot(this, context); } } export class TypeofExpression extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public expression: AST, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitTypeofExpression(this, context); } } export class VoidExpression extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public expression: AST, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitVoidExpression(this, context); } } export class NonNullAssert extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public expression: AST, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitNonNullAssert(this, context); } } export class Call extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public receiver: AST, public args: AST[], public argumentSpan: AbsoluteSourceSpan, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitCall(this, context); } } export class SafeCall extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public receiver: AST, public args: AST[], public argumentSpan: AbsoluteSourceSpan, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context: any = null): any { return visitor.visitSafeCall(this, context); } } export class TaggedTemplateLiteral extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public tag: AST, public template: TemplateLiteral, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context?: any) { return visitor.visitTaggedTemplateLiteral(this, context); } } export class TemplateLiteral extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public elements: TemplateLiteralElement[], public expressions: AST[], ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context?: any) { return visitor.visitTemplateLiteral(this, context); } } export class TemplateLiteralElement extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public text: string, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context?: any) { return visitor.visitTemplateLiteralElement(this, context); } } export class ParenthesizedExpression extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public expression: AST, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context?: any) { return visitor.visitParenthesizedExpression(this, context); } } export class ArrowFunctionIdentifierParameter { constructor( public name: string, public span: ParseSpan, public sourceSpan: AbsoluteSourceSpan, ) {} } export type ArrowFunctionParameter = ArrowFunctionIdentifierParameter; // TODO(crisbeto): also rest parameters? export class ArrowFunction extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public parameters: ArrowFunctionParameter[], public body: AST, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context?: any) { return visitor.visitArrowFunction(this, context); } } export class RegularExpressionLiteral extends AST { constructor( span: ParseSpan, sourceSpan: AbsoluteSourceSpan, readonly body: string, readonly flags: string | null, ) { super(span, sourceSpan); } override visit(visitor: AstVisitor, context?: any) { return visitor.visitRegularExpressionLiteral(this, context); } } /** * Records the absolute position of a text span in a source file, where `start` and `end` are the * starting and ending byte offsets, respectively, of the text span in a source file. */ export class AbsoluteSourceSpan { constructor( public readonly start: number, public readonly end: number, ) {} } export class ASTWithSource<T extends AST = AST> extends AST { constructor( public ast: T, public source: string | null, public location: string, absoluteOffset: number, public errors: ParseError[], ) { super( new ParseSpan(0, source === null ? 0 : source.length), new AbsoluteSourceSpan( absoluteOffset, source === null ? absoluteOffset : absoluteOffset + source.length, ), ); } override visit(visitor: AstVisitor, context: any = null): any { if (visitor.visitASTWithSource) { return visitor.visitASTWithSource(this, context); } return this.ast.visit(visitor, context); } override toString(): string { return `${this.source} in ${this.location}`; } } /** * TemplateBinding refers to a particular key-value pair in a microsyntax * expression. A few examples are: * * |---------------------|--------------|---------|--------------| * | expression | key | value | binding type | * |---------------------|--------------|---------|--------------| * | 1. let item | item | null | variable | * | 2. of items | ngForOf | items | expression | * | 3. let x = y | x | y | variable | * | 4. index as i | i | index | variable | * | 5. trackBy: func | ngForTrackBy | func | expression | * | 6. *ngIf="cond" | ngIf | cond | expression | * |---------------------|--------------|---------|--------------| * * (6) is a notable exception because it is a binding from the template key in * the LHS of a HTML attribute to the expression in the RHS. All other bindings * in the example above are derived solely from the RHS. */ export type TemplateBinding = VariableBinding | ExpressionBinding; export class VariableBinding { /** * @param sourceSpan entire span of the binding. * @param key name of the LHS along with its span. * @param value optional value for the RHS along with its span. */ constructor( public readonly sourceSpan: AbsoluteSourceSpan, public readonly key: TemplateBindingIdentifier, public readonly value: TemplateBindingIdentifier | null, ) {} } export class ExpressionBinding { /** * @param sourceSpan entire span of the binding. * @param key binding name, like ngForOf, ngForTrackBy, ngIf, along with its * span. Note that the length of the span may not be the same as * `key.source.length`. For example, * 1. key.source = ngFor, key.span is for "ngFor" * 2. key.source = ngForOf, key.span is for "of" * 3. key.source = ngForTrackBy, key.span is for "trackBy" * @param value optional expression for the RHS. */ constructor( public readonly sourceSpan: AbsoluteSourceSpan, public readonly key: TemplateBindingIdentifier, public readonly value: ASTWithSource | null, ) {} } export interface TemplateBindingIdentifier { source: string; span: AbsoluteSourceSpan; } export interface AstVisitor { /** * The `visitUnary` method is declared as optional for backwards compatibility. In an upcoming * major release, this method will be made required. */ visitUnary?(ast: Unary, context: any): any; visitBinary(ast: Binary, context: any): any; visitChain(ast: Chain, context: any): any; visitConditional(ast: Conditional, context: any): any; /** * The `visitThisReceiver` method is declared as optional for backwards compatibility. * In an upcoming major release, this method will be made required. */ visitThisReceiver?(ast: ThisReceiver, context: any): any; visitImplicitReceiver(ast: ImplicitReceiver, context: any): any; visitInterpolation(ast: Interpolation, context: any): any; visitKeyedRead(ast: KeyedRead, context: any): any; visitLiteralArray(ast: LiteralArray, context: any): any; visitLiteralMap(ast: LiteralMap, context: any): any; visitLiteralPrimitive(ast: LiteralPrimitive, context: any): any; visitPipe(ast: BindingPipe, context: any): any; visitPrefixNot(ast: PrefixNot, context: any): any; visitTypeofExpression(ast: TypeofExpression, context: any): any; visitVoidExpression(ast: TypeofExpression, context: any): any; visitNonNullAssert(ast: NonNullAssert, context: any): any; visitPropertyRead(ast: PropertyRead, context: any): any; visitSafePropertyRead(ast: SafePropertyRead, context: any): any; visitSafeKeyedRead(ast: SafeKeyedRead, context: any): any; visitCall(ast: Call, context: any): any; visitSafeCall(ast: SafeCall, context: any): any; visitTemplateLiteral(ast: TemplateLiteral, context: any): any; visitTemplateLiteralElement(ast: TemplateLiteralElement, context: any): any; visitTaggedTemplateLiteral(ast: TaggedTemplateLiteral, context: any): any; visitParenthesizedExpression(ast: ParenthesizedExpression, context: any): any; visitArrowFunction(ast: ArrowFunction, context: any): any; visitRegularExpressionLiteral(ast: RegularExpressionLiteral, context: any): any; visitSpreadElement(ast: SpreadElement, context: any): any; visitASTWithSource?(ast: ASTWithSource, context: any): any; visitEmptyExpr?(ast: EmptyExpr, context: any): any; /** * This function is optionally defined to allow classes that implement this * interface to selectively decide if the specified `ast` should be visited. * @param ast node to visit * @param context context that gets passed to the node and all its children */ visit?(ast: AST, context?: any): any; } export class RecursiveAstVisitor implements AstVisitor { visit(ast: AST, context?: any): any { // The default implementation just visits every node. // Classes that extend RecursiveAstVisitor should override this function // to selectively visit the specified node. ast.visit(this, context); } visitUnary(ast: Unary, context: any): any { this.visit(ast.expr, context); } visitBinary(ast: Binary, context: any): any { this.visit(ast.left, context); this.visit(ast.right, context); } visitChain(ast: Chain, context: any): any { this.visitAll(ast.expressions, context); } visitConditional(ast: Conditional, context: any): any { this.visit(ast.condition, context); this.visit(ast.trueExp, context); this.visit(ast.falseExp, context); } visitPipe(ast: BindingPipe, context: any): any { this.visit(ast.exp, context); this.visitAll(ast.args, context); } visitImplicitReceiver(ast: ThisReceiver, context: any): any {} visitThisReceiver(ast: ThisReceiver, context: any): any {} visitInterpolation(ast: Interpolation, context: any): any { this.visitAll(ast.expressions, context); } visitKeyedRead(ast: KeyedRead, context: any): any { this.visit(ast.receiver, context); this.visit(ast.key, context); } visitLiteralArray(ast: LiteralArray, context: any): any { this.visitAll(ast.expressions, context); } visitLiteralMap(ast: LiteralMap, context: any): any { this.visitAll(ast.values, context); } visitLiteralPrimitive(ast: LiteralPrimitive, context: any): any {} visitPrefixNot(ast: PrefixNot, context: any): any { this.visit(ast.expression, context); } visitTypeofExpression(ast: TypeofExpression, context: any) { this.visit(ast.expression, context); } visitVoidExpression(ast: VoidExpression, context: any) { this.visit(ast.expression, context); } visitNonNullAssert(ast: NonNullAssert, context: any): any { this.visit(ast.expression, context); } visitPropertyRead(ast: PropertyRead, context: any): any { this.visit(ast.receiver, context); } visitSafePropertyRead(ast: SafePropertyRead, context: any): any { this.visit(ast.receiver, context); } visitSafeKeyedRead(ast: SafeKeyedRead, context: any): any { this.visit(ast.receiver, context); this.visit(ast.key, context); } visitCall(ast: Call, context: any): any { this.visit(ast.receiver, context); this.visitAll(ast.args, context); } visitSafeCall(ast: SafeCall, context: any): any { this.visit(ast.receiver, context); this.visitAll(ast.args, context); } visitTemplateLiteral(ast: TemplateLiteral, context: any) { // Iterate in the declaration order. Note that there will // always be one expression less than the number of elements. for (let i = 0; i < ast.elements.length; i++) { this.visit(ast.elements[i], context); const expression = i < ast.expressions.length ? ast.expressions[i] : null; if (expression !== null) { this.visit(expression, context); } } } visitTemplateLiteralElement(ast: TemplateLiteralElement, context: any) {} visitTaggedTemplateLiteral(ast: TaggedTemplateLiteral, context: any) { this.visit(ast.tag, context); this.visit(ast.template, context); } visitParenthesizedExpression(ast: ParenthesizedExpression, context: any) { this.visit(ast.expression, context); } visitArrowFunction(ast: ArrowFunction, context: any): any { this.visit(ast.body, context); } visitRegularExpressionLiteral(ast: RegularExpressionLiteral, context: any) {} visitSpreadElement(ast: SpreadElement, context: any) { this.visit(ast.expression, context); } visitEmptyExpr(ast: EmptyExpr, context: any): any {} // This is not part of the AstVisitor interface, just a helper method visitAll(asts: AST[], context: any): any { for (const ast of asts) { this.visit(ast, context); } } } // Bindings export class ParsedProperty { public readonly isLiteral: boolean; public readonly isLegacyAnimation: boolean; public readonly isAnimation: boolean; constructor( public name: string, public expression: ASTWithSource, public type: ParsedPropertyType, public sourceSpan: ParseSourceSpan, readonly keySpan: ParseSourceSpan, public valueSpan: ParseSourceSpan | undefined, ) { this.isLiteral = this.type === ParsedPropertyType.LITERAL_ATTR; this.isLegacyAnimation = this.type === ParsedPropertyType.LEGACY_ANIMATION; this.isAnimation = this.type === ParsedPropertyType.ANIMATION; } } export enum ParsedPropertyType { DEFAULT, LITERAL_ATTR, LEGACY_ANIMATION, TWO_WAY, ANIMATION, } export enum ParsedEventType { // DOM or Directive event Regular, // Legacy animation specific event LegacyAnimation, // Event side of a two-way binding (e.g. `[(property)]="expression"`). TwoWay, // Animation specific event Animation, } export class ParsedEvent { // Regular events have a target // Legacy Animation events have a phase constructor( name: string, targetOrPhase: string | null, type: ParsedEventType.TwoWay, handler: ASTWithSource<NonNullAssert | PropertyRead | KeyedRead>, sourceSpan: ParseSourceSpan, handlerSpan: ParseSourceSpan, keySpan: ParseSourceSpan, ); constructor( name: string, targetOrPhase: string | null, type: ParsedEventType, handler: ASTWithSource, sourceSpan: ParseSourceSpan, handlerSpan: ParseSourceSpan, keySpan: ParseSourceSpan, ); constructor( public name: string, public targetOrPhase: string | null, public type: ParsedEventType, public handler: ASTWithSource, public sourceSpan: ParseSourceSpan, public handlerSpan: ParseSourceSpan, readonly keySpan: ParseSourceSpan, ) {} } /** * ParsedVariable represents a variable declaration in a microsyntax expression. */ export class ParsedVariable { constructor( public readonly name: string, public readonly value: string, public readonly sourceSpan: ParseSourceSpan, public readonly keySpan: ParseSourceSpan, public readonly valueSpan?: ParseSourceSpan, ) {} } export enum BindingType { // A regular binding to a property (e.g. `[property]="expression"`). Property, // A binding to an element attribute (e.g. `[attr.name]="expression"`). Attribute, // A binding to a CSS class (e.g. `[class.name]="condition"`). Class, // A binding to a style rule (e.g. `[style.rule]="expression"`). Style, // A binding to a legacy animation reference (e.g. `[animate.key]="expression"`). LegacyAnimation, // Property side of a two-way binding (e.g. `[(property)]="expression"`). TwoWay, // A binding to an animation CSS class or function (e.g. `[animate.leave]="expression"`). Animation, } export class BoundElementProperty { constructor( public name: string, public type: BindingType, public securityContext: SecurityContext, public value: ASTWithSource, public unit: string | null, public sourceSpan: ParseSourceSpan, readonly keySpan: ParseSourceSpan | undefined, public valueSpan: ParseSourceSpan | undefined, ) {} }