angular/packages/compiler/test/render3/view/util.ts
Matthieu Riegler 04462ed67f refactor(compiler): Remove the interpolation config (#64071)
After #63474, we don't need that anymore.

PR Close #64071
2025-09-29 15:29:46 -04:00

200 lines
6.5 KiB
TypeScript

/**
* @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 * as e from '../../../src/expression_parser/ast';
import {Lexer} from '../../../src/expression_parser/lexer';
import {Parser} from '../../../src/expression_parser/parser';
import * as html from '../../../src/ml_parser/ast';
import {HtmlParser} from '../../../src/ml_parser/html_parser';
import {WhitespaceVisitor, visitAllWithSiblings} from '../../../src/ml_parser/html_whitespaces';
import {ParseTreeResult} from '../../../src/ml_parser/parser';
import * as a from '../../../src/render3/r3_ast';
import {htmlAstToRender3Ast, Render3ParseResult} from '../../../src/render3/r3_template_transform';
import {I18nMetaVisitor} from '../../../src/render3/view/i18n/meta';
import {LEADING_TRIVIA_CHARS} from '../../../src/render3/view/template';
import {ElementSchemaRegistry} from '../../../src/schema/element_schema_registry';
import {BindingParser} from '../../../src/template_parser/binding_parser';
class MockSchemaRegistry implements ElementSchemaRegistry {
constructor(
public existingProperties: {[key: string]: boolean},
public attrPropMapping: {[key: string]: string},
public existingElements: {[key: string]: boolean},
public invalidProperties: Array<string>,
public invalidAttributes: Array<string>,
) {}
hasProperty(tagName: string, property: string, schemas: any[]): boolean {
const value = this.existingProperties[property];
return value === void 0 ? true : value;
}
hasElement(tagName: string, schemaMetas: any[]): boolean {
const value = this.existingElements[tagName.toLowerCase()];
return value === void 0 ? true : value;
}
allKnownElementNames(): string[] {
return Object.keys(this.existingElements);
}
securityContext(selector: string, property: string, isAttribute: boolean): any {
return 0;
}
getMappedPropName(attrName: string): string {
return this.attrPropMapping[attrName] || attrName;
}
getDefaultComponentElementName(): string {
return 'ng-component';
}
validateProperty(name: string): {error: boolean; msg?: string} {
if (this.invalidProperties.indexOf(name) > -1) {
return {error: true, msg: `Binding to property '${name}' is disallowed for security reasons`};
} else {
return {error: false};
}
}
validateAttribute(name: string): {error: boolean; msg?: string} {
if (this.invalidAttributes.indexOf(name) > -1) {
return {
error: true,
msg: `Binding to attribute '${name}' is disallowed for security reasons`,
};
} else {
return {error: false};
}
}
normalizeAnimationStyleProperty(propName: string): string {
return propName;
}
normalizeAnimationStyleValue(
camelCaseProp: string,
userProvidedProp: string,
val: string | number,
): {error: string; value: string} {
return {error: null!, value: val.toString()};
}
}
export function findExpression(tmpl: a.Node[], expr: string): e.AST | null {
const res = tmpl.reduce(
(found, node) => {
if (found !== null) {
return found;
} else {
return findExpressionInNode(node, expr);
}
},
null as e.AST | null,
);
if (res instanceof e.ASTWithSource) {
return res.ast;
}
return res;
}
function findExpressionInNode(node: a.Node, expr: string): e.AST | null {
if (node instanceof a.Element || node instanceof a.Template || node instanceof a.Component) {
return findExpression([...node.inputs, ...node.outputs, ...node.children], expr);
} else if (node instanceof a.Directive) {
return findExpression([...node.inputs, ...node.outputs], expr);
} else if (node instanceof a.BoundAttribute || node instanceof a.BoundText) {
const ts = toStringExpression(node.value);
return ts === expr ? node.value : null;
} else if (node instanceof a.BoundEvent) {
return toStringExpression(node.handler) === expr ? node.handler : null;
} else {
return null;
}
}
export function toStringExpression(expr: e.AST): string {
while (expr instanceof e.ASTWithSource) {
expr = expr.ast;
}
if (expr instanceof e.PropertyRead) {
if (expr.receiver instanceof e.ImplicitReceiver) {
return expr.name;
} else {
return `${toStringExpression(expr.receiver)}.${expr.name}`;
}
} else if (expr instanceof e.ImplicitReceiver) {
return '';
} else if (expr instanceof e.Interpolation) {
let str = '{{';
for (let i = 0; i < expr.expressions.length; i++) {
str += expr.strings[i] + toStringExpression(expr.expressions[i]);
}
str += expr.strings[expr.strings.length - 1] + '}}';
return str;
} else {
throw new Error(`Unsupported type: ${(expr as any).constructor.name}`);
}
}
// Parse an html string to IVY specific info
export function parseR3(
input: string,
options: {
preserveWhitespaces?: boolean;
leadingTriviaChars?: string[];
ignoreError?: boolean;
selectorlessEnabled?: boolean;
} = {},
): Render3ParseResult {
const htmlParser = new HtmlParser();
const parseResult = htmlParser.parse(input, 'path:://to/template', {
tokenizeExpansionForms: true,
leadingTriviaChars: options.leadingTriviaChars ?? LEADING_TRIVIA_CHARS,
selectorlessEnabled: options.selectorlessEnabled,
});
if (parseResult.errors.length > 0 && !options.ignoreError) {
const msg = parseResult.errors.map((e) => e.toString()).join('\n');
throw new Error(msg);
}
let htmlNodes = processI18nMeta(parseResult).rootNodes;
if (!options.preserveWhitespaces) {
htmlNodes = visitAllWithSiblings(
new WhitespaceVisitor(true /* preserveSignificantWhitespace */),
htmlNodes,
);
}
const expressionParser = new Parser(new Lexer());
const schemaRegistry = new MockSchemaRegistry(
{'invalidProp': false},
{'mappedAttr': 'mappedProp'},
{'unknown': false, 'un-known': false},
['onEvent'],
['onEvent'],
);
const bindingParser = new BindingParser(expressionParser, schemaRegistry, []);
const r3Result = htmlAstToRender3Ast(htmlNodes, bindingParser, {collectCommentNodes: false});
if (r3Result.errors.length > 0 && !options.ignoreError) {
const msg = r3Result.errors.map((e) => e.toString()).join('\n');
throw new Error(msg);
}
return r3Result;
}
export function processI18nMeta(htmlAstWithErrors: ParseTreeResult): ParseTreeResult {
return new ParseTreeResult(
html.visitAll(new I18nMetaVisitor(/* keepI18nAttrs */ false), htmlAstWithErrors.rootNodes),
htmlAstWithErrors.errors,
);
}