refactor(core): migrations do not properly handle multiple templates in source file (#29841)

Currently if there are multiple source files within a given
TypeScript source file, only the last template in the source
file is checked as we store templates in a `Map` with the
source file paths as keys.

This is problematic as multiple templates can live within the
same source file and we therefore accidentally overwrite
existing entries in the resolved templates map.

PR Close #29841
This commit is contained in:
Paul Gschwendtner 2019-04-11 18:59:12 +02:00 committed by Alex Rickabaugh
parent 446e3573e3
commit b0c1282fbe
5 changed files with 34 additions and 10 deletions

View File

@ -24,9 +24,9 @@ export interface TemplateVariableAssignment {
* Analyzes a given resolved template by looking for property assignments to local * Analyzes a given resolved template by looking for property assignments to local
* template variables within bound events. * template variables within bound events.
*/ */
export function analyzeResolvedTemplate( export function analyzeResolvedTemplate(template: ResolvedTemplate): TemplateVariableAssignment[]|
filePath: string, template: ResolvedTemplate): TemplateVariableAssignment[]|null { null {
const templateNodes = parseHtmlGracefully(template.content, filePath); const templateNodes = parseHtmlGracefully(template.content, template.filePath);
if (!templateNodes) { if (!templateNodes) {
return null; return null;

View File

@ -33,8 +33,9 @@ export class Rule extends Rules.TypedRule {
// Analyze each resolved template and print a warning for property writes to // Analyze each resolved template and print a warning for property writes to
// template variables. // template variables.
resolvedTemplates.forEach((template, filePath) => { resolvedTemplates.forEach(template => {
const nodes = analyzeResolvedTemplate(filePath, template); const filePath = template.filePath;
const nodes = analyzeResolvedTemplate(template);
const templateFile = const templateFile =
template.inline ? sourceFile : createHtmlSourceFile(filePath, template.content); template.inline ? sourceFile : createHtmlSourceFile(filePath, template.content);

View File

@ -71,8 +71,9 @@ function runTemplateVariableAssignmentCheck(
// Analyze each resolved template and print a warning for property writes to // Analyze each resolved template and print a warning for property writes to
// template variables. // template variables.
resolvedTemplates.forEach((template, filePath) => { resolvedTemplates.forEach(template => {
const nodes = analyzeResolvedTemplate(filePath, template); const filePath = template.filePath;
const nodes = analyzeResolvedTemplate(template);
if (!nodes) { if (!nodes) {
return; return;

View File

@ -197,4 +197,26 @@ describe('template variable assignment migration', () => {
expect(warnOutput.length).toBe(0); expect(warnOutput.length).toBe(0);
}); });
it('should be able to report multiple templates within the same source file', () => {
writeFile('/index.ts', `
import {Component} from '@angular/core';
@Component({
template: '<ng-template let-one><a (sayHello)="one=true"></a></ng-template>',
})
export class MyComp {}
@Component({
template: '<ng-template let-two><b (greet)="two=true"></b></ng-template>',
})
export class MyComp2 {}
`);
runMigration();
expect(warnOutput.length).toBe(2);
expect(warnOutput[0]).toMatch(/^⮑ {3}index.ts@5:56: Found assignment/);
expect(warnOutput[1]).toMatch(/^⮑ {3}index.ts@10:53: Found assignment/);
});
}); });

View File

@ -39,7 +39,7 @@ export interface ResolvedTemplate {
* TypeScript source files (inline templates or external referenced templates) * TypeScript source files (inline templates or external referenced templates)
*/ */
export class NgComponentTemplateVisitor { export class NgComponentTemplateVisitor {
resolvedTemplates = new Map<string, ResolvedTemplate>(); resolvedTemplates: ResolvedTemplate[] = [];
constructor(public typeChecker: ts.TypeChecker) {} constructor(public typeChecker: ts.TypeChecker) {}
@ -95,7 +95,7 @@ export class NgComponentTemplateVisitor {
// not part of the template content. // not part of the template content.
const templateStartIdx = property.initializer.getStart() + 1; const templateStartIdx = property.initializer.getStart() + 1;
const filePath = resolve(sourceFileName); const filePath = resolve(sourceFileName);
this.resolvedTemplates.set(filePath, { this.resolvedTemplates.push({
filePath: filePath, filePath: filePath,
container: node, container: node,
content: property.initializer.text, content: property.initializer.text,
@ -117,7 +117,7 @@ export class NgComponentTemplateVisitor {
const fileContent = readFileSync(templatePath, 'utf8'); const fileContent = readFileSync(templatePath, 'utf8');
const lineStartsMap = computeLineStartsMap(fileContent); const lineStartsMap = computeLineStartsMap(fileContent);
this.resolvedTemplates.set(templatePath, { this.resolvedTemplates.push({
filePath: templatePath, filePath: templatePath,
container: node, container: node,
content: fileContent, content: fileContent,