refactor(compiler): use options
argument for parsers (#28055)
This commit consolidates the options that can modify the parsing of text (e.g. HTML, Angular templates, CSS, i18n) into an AST for further processing into a single `options` hash. This makes the code cleaner and more readable, but also enables us to support further options to parsing without triggering wide ranging changes to code that should not be affected by these new options. Specifically, it will let us pass information about the placement of a template that is being parsed in its containing file, which is essential for accurate SourceMap processing. PR Close #28055
This commit is contained in:

committed by
Misko Hevery

parent
81df5dcfc0
commit
673ac2945c
@ -113,12 +113,11 @@ export class DirectiveNormalizer {
|
||||
templateAbsUrl: string): PreparsedTemplate {
|
||||
const isInline = !!prenormData.template;
|
||||
const interpolationConfig = InterpolationConfig.fromArray(prenormData.interpolation !);
|
||||
const templateUrl = templateSourceUrl(
|
||||
{reference: prenormData.ngModuleType}, {type: {reference: prenormData.componentType}},
|
||||
{isInline, templateUrl: templateAbsUrl});
|
||||
const rootNodesAndErrors = this._htmlParser.parse(
|
||||
template,
|
||||
templateSourceUrl(
|
||||
{reference: prenormData.ngModuleType}, {type: {reference: prenormData.componentType}},
|
||||
{isInline, templateUrl: templateAbsUrl}),
|
||||
true, interpolationConfig);
|
||||
template, templateUrl, {tokenizeExpansionForms: true, interpolationConfig});
|
||||
if (rootNodesAndErrors.errors.length > 0) {
|
||||
const errorString = rootNodesAndErrors.errors.join('\n');
|
||||
throw syntaxError(`Template parse errors:\n${errorString}`);
|
||||
|
@ -8,7 +8,8 @@
|
||||
|
||||
import {MissingTranslationStrategy} from '../core';
|
||||
import {HtmlParser} from '../ml_parser/html_parser';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG} from '../ml_parser/interpolation_config';
|
||||
import {TokenizeOptions} from '../ml_parser/lexer';
|
||||
import {ParseTreeResult} from '../ml_parser/parser';
|
||||
import {Console} from '../util';
|
||||
|
||||
@ -41,11 +42,9 @@ export class I18NHtmlParser implements HtmlParser {
|
||||
}
|
||||
}
|
||||
|
||||
parse(
|
||||
source: string, url: string, parseExpansionForms: boolean = false,
|
||||
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ParseTreeResult {
|
||||
const parseResult =
|
||||
this._htmlParser.parse(source, url, parseExpansionForms, interpolationConfig);
|
||||
parse(source: string, url: string, options: TokenizeOptions = {}): ParseTreeResult {
|
||||
const interpolationConfig = options.interpolationConfig || DEFAULT_INTERPOLATION_CONFIG;
|
||||
const parseResult = this._htmlParser.parse(source, url, {interpolationConfig, ...options});
|
||||
|
||||
if (parseResult.errors.length) {
|
||||
return new ParseTreeResult(parseResult.rootNodes, parseResult.errors);
|
||||
|
@ -27,7 +27,8 @@ export class MessageBundle {
|
||||
|
||||
updateFromTemplate(html: string, url: string, interpolationConfig: InterpolationConfig):
|
||||
ParseError[] {
|
||||
const htmlParserResult = this._htmlParser.parse(html, url, true, interpolationConfig);
|
||||
const htmlParserResult =
|
||||
this._htmlParser.parse(html, url, {tokenizeExpansionForms: true, interpolationConfig});
|
||||
|
||||
if (htmlParserResult.errors.length) {
|
||||
return htmlParserResult.errors;
|
||||
|
@ -185,7 +185,7 @@ class XliffParser implements ml.Visitor {
|
||||
this._unitMlString = null;
|
||||
this._msgIdToHtml = {};
|
||||
|
||||
const xml = new XmlParser().parse(xliff, url, false);
|
||||
const xml = new XmlParser().parse(xliff, url);
|
||||
|
||||
this._errors = xml.errors;
|
||||
ml.visitAll(this, xml.rootNodes, null);
|
||||
@ -268,7 +268,7 @@ class XmlToI18n implements ml.Visitor {
|
||||
private _errors !: I18nError[];
|
||||
|
||||
convert(message: string, url: string) {
|
||||
const xmlIcu = new XmlParser().parse(message, url, true);
|
||||
const xmlIcu = new XmlParser().parse(message, url, {tokenizeExpansionForms: true});
|
||||
this._errors = xmlIcu.errors;
|
||||
|
||||
const i18nNodes = this._errors.length > 0 || xmlIcu.rootNodes.length == 0 ?
|
||||
|
@ -203,7 +203,7 @@ class Xliff2Parser implements ml.Visitor {
|
||||
this._unitMlString = null;
|
||||
this._msgIdToHtml = {};
|
||||
|
||||
const xml = new XmlParser().parse(xliff, url, false);
|
||||
const xml = new XmlParser().parse(xliff, url);
|
||||
|
||||
this._errors = xml.errors;
|
||||
ml.visitAll(this, xml.rootNodes, null);
|
||||
@ -293,7 +293,7 @@ class XmlToI18n implements ml.Visitor {
|
||||
private _errors !: I18nError[];
|
||||
|
||||
convert(message: string, url: string) {
|
||||
const xmlIcu = new XmlParser().parse(message, url, true);
|
||||
const xmlIcu = new XmlParser().parse(message, url, {tokenizeExpansionForms: true});
|
||||
this._errors = xmlIcu.errors;
|
||||
|
||||
const i18nNodes = this._errors.length > 0 || xmlIcu.rootNodes.length == 0 ?
|
||||
|
@ -88,7 +88,7 @@ class XtbParser implements ml.Visitor {
|
||||
|
||||
// We can not parse the ICU messages at this point as some messages might not originate
|
||||
// from Angular that could not be lex'd.
|
||||
const xml = new XmlParser().parse(xtb, url, false);
|
||||
const xml = new XmlParser().parse(xtb, url);
|
||||
|
||||
this._errors = xml.errors;
|
||||
ml.visitAll(this, xml.rootNodes);
|
||||
@ -159,7 +159,7 @@ class XmlToI18n implements ml.Visitor {
|
||||
private _errors !: I18nError[];
|
||||
|
||||
convert(message: string, url: string) {
|
||||
const xmlIcu = new XmlParser().parse(message, url, true);
|
||||
const xmlIcu = new XmlParser().parse(message, url, {tokenizeExpansionForms: true});
|
||||
this._errors = xmlIcu.errors;
|
||||
|
||||
const i18nNodes = this._errors.length > 0 || xmlIcu.rootNodes.length == 0 ?
|
||||
|
@ -83,7 +83,7 @@ class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
|
||||
// text to html
|
||||
const url = srcMsg.nodes[0].sourceSpan.start.file.url;
|
||||
const html = new HtmlParser().parse(text, url, true);
|
||||
const html = new HtmlParser().parse(text, url, {tokenizeExpansionForms: true});
|
||||
|
||||
return {
|
||||
nodes: html.rootNodes,
|
||||
|
@ -112,7 +112,7 @@ export class CompilerFacadeImpl implements CompilerFacade {
|
||||
// Parse the template and check for errors.
|
||||
const template = parseTemplate(
|
||||
facade.template, sourceMapUrl,
|
||||
{preserveWhitespaces: facade.preserveWhitespaces || false, interpolationConfig});
|
||||
{preserveWhitespaces: facade.preserveWhitespaces, interpolationConfig});
|
||||
if (template.errors !== undefined) {
|
||||
const errors = template.errors.map(err => err.toString()).join(', ');
|
||||
throw new Error(`Errors during JIT compilation of template for ${facade.name}: ${errors}`);
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {getHtmlTagDefinition} from './html_tags';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config';
|
||||
import {TokenizeOptions} from './lexer';
|
||||
import {ParseTreeResult, Parser} from './parser';
|
||||
|
||||
export {ParseTreeResult, TreeError} from './parser';
|
||||
@ -15,9 +15,7 @@ export {ParseTreeResult, TreeError} from './parser';
|
||||
export class HtmlParser extends Parser {
|
||||
constructor() { super(getHtmlTagDefinition); }
|
||||
|
||||
parse(
|
||||
source: string, url: string, parseExpansionForms: boolean = false,
|
||||
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ParseTreeResult {
|
||||
return super.parse(source, url, parseExpansionForms, interpolationConfig);
|
||||
parse(source: string, url: string, options?: TokenizeOptions): ParseTreeResult {
|
||||
return super.parse(source, url, options);
|
||||
}
|
||||
}
|
||||
|
@ -49,14 +49,20 @@ export class TokenizeResult {
|
||||
constructor(public tokens: Token[], public errors: TokenError[]) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options that modify how the text is tokenized.
|
||||
*/
|
||||
export interface TokenizeOptions {
|
||||
/** Whether to tokenize ICU messages (considered as text nodes when false). */
|
||||
tokenizeExpansionForms?: boolean;
|
||||
/** How to tokenize interpolation markers. */
|
||||
interpolationConfig?: InterpolationConfig;
|
||||
}
|
||||
|
||||
export function tokenize(
|
||||
source: string, url: string, getTagDefinition: (tagName: string) => TagDefinition,
|
||||
tokenizeExpansionForms: boolean = false,
|
||||
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): TokenizeResult {
|
||||
return new _Tokenizer(
|
||||
new ParseSourceFile(source, url), getTagDefinition, tokenizeExpansionForms,
|
||||
interpolationConfig)
|
||||
.tokenize();
|
||||
options: TokenizeOptions = {}): TokenizeResult {
|
||||
return new _Tokenizer(new ParseSourceFile(source, url), getTagDefinition, options).tokenize();
|
||||
}
|
||||
|
||||
const _CR_OR_CRLF_REGEXP = /\r\n?/g;
|
||||
@ -78,6 +84,8 @@ class _ControlFlowError {
|
||||
class _Tokenizer {
|
||||
private _input: string;
|
||||
private _length: number;
|
||||
private _tokenizeIcu: boolean;
|
||||
private _interpolationConfig: InterpolationConfig;
|
||||
// Note: this is always lowercase!
|
||||
private _peek: number = -1;
|
||||
private _nextPeek: number = -1;
|
||||
@ -102,8 +110,9 @@ class _Tokenizer {
|
||||
*/
|
||||
constructor(
|
||||
private _file: ParseSourceFile, private _getTagDefinition: (tagName: string) => TagDefinition,
|
||||
private _tokenizeIcu: boolean,
|
||||
private _interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
|
||||
options: TokenizeOptions) {
|
||||
this._tokenizeIcu = options.tokenizeExpansionForms || false;
|
||||
this._interpolationConfig = options.interpolationConfig || DEFAULT_INTERPOLATION_CONFIG;
|
||||
this._input = _file.content;
|
||||
this._length = _file.content.length;
|
||||
this._advance();
|
||||
|
@ -9,7 +9,6 @@
|
||||
import {ParseError, ParseSourceSpan} from '../parse_util';
|
||||
|
||||
import * as html from './ast';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config';
|
||||
import * as lex from './lexer';
|
||||
import {TagDefinition, getNsPrefix, isNgContainer, mergeNsAndName} from './tags';
|
||||
|
||||
@ -30,11 +29,8 @@ export class ParseTreeResult {
|
||||
export class Parser {
|
||||
constructor(public getTagDefinition: (tagName: string) => TagDefinition) {}
|
||||
|
||||
parse(
|
||||
source: string, url: string, parseExpansionForms: boolean = false,
|
||||
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ParseTreeResult {
|
||||
const tokensAndErrors =
|
||||
lex.tokenize(source, url, this.getTagDefinition, parseExpansionForms, interpolationConfig);
|
||||
parse(source: string, url: string, options?: lex.TokenizeOptions): ParseTreeResult {
|
||||
const tokensAndErrors = lex.tokenize(source, url, this.getTagDefinition, options);
|
||||
|
||||
const treeAndErrors = new _TreeBuilder(tokensAndErrors.tokens, this.getTagDefinition).build();
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {TokenizeOptions} from './lexer';
|
||||
import {ParseTreeResult, Parser} from './parser';
|
||||
import {getXmlTagDefinition} from './xml_tags';
|
||||
|
||||
@ -14,7 +15,7 @@ export {ParseTreeResult, TreeError} from './parser';
|
||||
export class XmlParser extends Parser {
|
||||
constructor() { super(getXmlTagDefinition); }
|
||||
|
||||
parse(source: string, url: string, parseExpansionForms: boolean = false): ParseTreeResult {
|
||||
return super.parse(source, url, parseExpansionForms);
|
||||
parse(source: string, url: string, options?: TokenizeOptions): ParseTreeResult {
|
||||
return super.parse(source, url, options);
|
||||
}
|
||||
}
|
||||
|
@ -1562,20 +1562,35 @@ function interpolate(args: o.Expression[]): o.Expression {
|
||||
return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Options that can be used to modify how a template is parsed by `parseTemplate()`.
|
||||
*/
|
||||
export interface ParseTemplateOptions {
|
||||
/**
|
||||
* Include whitespace nodes in the parsed output.
|
||||
*/
|
||||
preserveWhitespaces?: boolean;
|
||||
/**
|
||||
* How to parse interpolation markers.
|
||||
*/
|
||||
interpolationConfig?: InterpolationConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a template into render3 `Node`s and additional metadata, with no other dependencies.
|
||||
*
|
||||
* @param template text of the template to parse
|
||||
* @param templateUrl URL to use for source mapping of the parsed template
|
||||
* @param options options to modify how the template is parsed
|
||||
*/
|
||||
export function parseTemplate(
|
||||
template: string, templateUrl: string,
|
||||
options: {preserveWhitespaces?: boolean, interpolationConfig?: InterpolationConfig} = {}):
|
||||
{errors?: ParseError[], nodes: t.Node[]} {
|
||||
options: ParseTemplateOptions = {}): {errors?: ParseError[], nodes: t.Node[]} {
|
||||
const {interpolationConfig, preserveWhitespaces} = options;
|
||||
const bindingParser = makeBindingParser(interpolationConfig);
|
||||
const htmlParser = new HtmlParser();
|
||||
const parseResult = htmlParser.parse(template, templateUrl, true, interpolationConfig);
|
||||
const parseResult =
|
||||
htmlParser.parse(template, templateUrl, {...options, tokenizeExpansionForms: true});
|
||||
|
||||
if (parseResult.errors && parseResult.errors.length > 0) {
|
||||
return {errors: parseResult.errors, nodes: []};
|
||||
|
@ -114,8 +114,10 @@ export class TemplateParser {
|
||||
directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
|
||||
templateUrl: string, preserveWhitespaces: boolean): TemplateParseResult {
|
||||
let htmlParseResult = typeof template === 'string' ?
|
||||
this._htmlParser !.parse(
|
||||
template, templateUrl, true, this.getInterpolationConfig(component)) :
|
||||
this._htmlParser !.parse(template, templateUrl, {
|
||||
tokenizeExpansionForms: true,
|
||||
interpolationConfig: this.getInterpolationConfig(component)
|
||||
}) :
|
||||
template;
|
||||
|
||||
if (!preserveWhitespaces) {
|
||||
|
Reference in New Issue
Block a user