From e7c36879369d91055b40053a77fd3e5cc3ff8d32 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 29 Dec 2020 14:36:56 +0000 Subject: [PATCH] refactor(compiler): synthesize external template node for partial compilation (#40237) When partially compiling a component with an external template, we must synthesize a new AST node for the string literal that holds the contents of the external template, since we want to source-map this expression directly back to the original external template file. PR Close #40237 --- .../src/ngtsc/annotations/src/component.ts | 8 ---- .../compiler/src/render3/partial/component.ts | 44 +++++++++++++++++-- .../compiler/src/render3/view/template.ts | 11 +++++ 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts index 7c2979297b..699cedcf4f 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts @@ -958,14 +958,6 @@ function sourceMapUrl(resourceUrl: string): string { * some of which might be useful for re-parsing the template with different options. */ export interface ParsedComponentTemplate extends ParsedTemplate { - /** - * A full path to the file which contains the template. - * - * This can be either the original .ts file if the template is inline, or the .html file if an - * external file was used. - */ - templateUrl: string; - /** * True if the original template was stored inline; * False if the template was in an external file. diff --git a/packages/compiler/src/render3/partial/component.ts b/packages/compiler/src/render3/partial/component.ts index 87de9be5c1..5a61485ad3 100644 --- a/packages/compiler/src/render3/partial/component.ts +++ b/packages/compiler/src/render3/partial/component.ts @@ -8,13 +8,14 @@ import * as core from '../../core'; import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config'; import * as o from '../../output/output_ast'; +import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '../../parse_util'; import {Identifiers as R3} from '../r3_identifiers'; import {DeclarationListEmitMode, R3ComponentDef, R3ComponentMetadata, R3UsedDirectiveMetadata} from '../view/api'; import {createComponentType} from '../view/compiler'; import {ParsedTemplate} from '../view/template'; import {DefinitionMap} from '../view/util'; -import {R3DeclareComponentMetadata} from './api'; +import {R3DeclareComponentMetadata} from './api'; import {createDirectiveDefinitionMap} from './directive'; import {toOptionalLiteralArray} from './util'; @@ -79,13 +80,50 @@ export function createComponentDefinitionMap(meta: R3ComponentMetadata, template */ function compileTemplateDefinition(template: ParsedTemplate): o.LiteralMapExpr { const templateMap = new DefinitionMap(); - const templateExpr = - typeof template.template === 'string' ? o.literal(template.template) : template.template; + const templateExpr = getTemplateExpression(template); templateMap.set('source', templateExpr); templateMap.set('isInline', o.literal(template.isInline)); return templateMap.toLiteralMap(); } +function getTemplateExpression(template: ParsedTemplate): o.Expression { + if (typeof template.template === 'string') { + if (template.isInline) { + // The template is inline but not a simple literal string, so give up with trying to + // source-map it and just return a simple literal here. + return o.literal(template.template); + } else { + // The template is external so we must synthesize an expression node with the appropriate + // source-span. + const contents = template.template; + const file = new ParseSourceFile(contents, template.templateUrl); + const start = new ParseLocation(file, 0, 0, 0); + const end = computeEndLocation(file, contents); + const span = new ParseSourceSpan(start, end); + return o.literal(contents, null, span); + } + } else { + // The template is inline so we can just reuse the current expression node. + return template.template; + } +} + +function computeEndLocation(file: ParseSourceFile, contents: string): ParseLocation { + const length = contents.length; + let lineStart = 0; + let lastLineStart = 0; + let line = 0; + do { + lineStart = contents.indexOf('\n', lastLineStart); + if (lineStart !== -1) { + lastLineStart = lineStart + 1; + line++; + } + } while (lineStart !== -1); + + return new ParseLocation(file, length, line, length - lastLineStart); +} + /** * Compiles the directives as registered in the component metadata into an array literal of the * individual directives. If the component does not use any directives, then null is returned. diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index e5fafe611f..a1e77ada50 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -2092,6 +2092,7 @@ export function parseTemplate( interpolationConfig, preserveWhitespaces, template, + templateUrl, isInline, errors: parseResult.errors, nodes: [], @@ -2117,6 +2118,7 @@ export function parseTemplate( interpolationConfig, preserveWhitespaces, template, + templateUrl, isInline, errors: i18nMetaResult.errors, nodes: [], @@ -2149,6 +2151,7 @@ export function parseTemplate( preserveWhitespaces, errors: errors.length > 0 ? errors : null, template, + templateUrl, isInline, nodes, styleUrls, @@ -2321,6 +2324,14 @@ export interface ParsedTemplate { */ template: string|o.Expression; + /** + * A full path to the file which contains the template. + * + * This can be either the original .ts file if the template is inline, or the .html file if an + * external file was used. + */ + templateUrl: string; + /** * Whether the template was inline (using `template`) or external (using `templateUrl`). */