Pete Bacon Darwin 8a54896a91 refactor(compiler): expose token parts in Text nodes (#42062)
When it was tokenized, text content is split into parts that can include
interpolations and encoded entities tokens.

To make this information available to downstream processing, this commit
adds these tokens to the `Text` AST nodes, with suitable processing.

PR Close #42062
2021-08-02 09:53:13 -07:00

93 lines
3.1 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.io/license
*/
import * as html from '../../src/ml_parser/ast';
import {ParseTreeResult} from '../../src/ml_parser/html_parser';
import {ParseLocation} from '../../src/parse_util';
export function humanizeDom(parseResult: ParseTreeResult, addSourceSpan: boolean = false): any[] {
if (parseResult.errors.length > 0) {
const errorString = parseResult.errors.join('\n');
throw new Error(`Unexpected parse errors:\n${errorString}`);
}
return humanizeNodes(parseResult.rootNodes, addSourceSpan);
}
export function humanizeDomSourceSpans(parseResult: ParseTreeResult): any[] {
return humanizeDom(parseResult, true);
}
export function humanizeNodes(nodes: html.Node[], addSourceSpan: boolean = false): any[] {
const humanizer = new _Humanizer(addSourceSpan);
html.visitAll(humanizer, nodes);
return humanizer.result;
}
export function humanizeLineColumn(location: ParseLocation): string {
return `${location.line}:${location.col}`;
}
class _Humanizer implements html.Visitor {
result: any[] = [];
elDepth: number = 0;
constructor(private includeSourceSpan: boolean) {}
visitElement(element: html.Element, context: any): any {
const res = this._appendContext(element, [html.Element, element.name, this.elDepth++]);
if (this.includeSourceSpan) {
res.push(element.startSourceSpan.toString() ?? null);
res.push(element.endSourceSpan?.toString() ?? null);
}
this.result.push(res);
html.visitAll(this, element.attrs);
html.visitAll(this, element.children);
this.elDepth--;
}
visitAttribute(attribute: html.Attribute, context: any): any {
const valueTokens = attribute.valueTokens ?? [];
const res = this._appendContext(attribute, [
html.Attribute, attribute.name, attribute.value, ...valueTokens.map(token => token.parts)
]);
this.result.push(res);
}
visitText(text: html.Text, context: any): any {
const res = this._appendContext(
text, [html.Text, text.value, this.elDepth, ...text.tokens.map(token => token.parts)]);
this.result.push(res);
}
visitComment(comment: html.Comment, context: any): any {
const res = this._appendContext(comment, [html.Comment, comment.value, this.elDepth]);
this.result.push(res);
}
visitExpansion(expansion: html.Expansion, context: any): any {
const res = this._appendContext(
expansion, [html.Expansion, expansion.switchValue, expansion.type, this.elDepth++]);
this.result.push(res);
html.visitAll(this, expansion.cases);
this.elDepth--;
}
visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any {
const res =
this._appendContext(expansionCase, [html.ExpansionCase, expansionCase.value, this.elDepth]);
this.result.push(res);
}
private _appendContext(ast: html.Node, input: any[]): any[] {
if (!this.includeSourceSpan) return input;
input.push(ast.sourceSpan.toString());
return input;
}
}