Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2c41bb8

Browse files
alxhubatscott
authored andcommitted
fix(compiler): type-checking error for duplicate variables in templates (#35674)
It's an error to declare a variable twice on a specific template: ```html <div *ngFor="let i of items; let i = index"> </div> ``` This commit introduces a template type-checking error which helps to detect and diagnose this problem. Fixes #35186 PR Close #35674
1 parent 1f8a243 commit 2c41bb8

File tree

6 files changed

+83
-0
lines changed

6 files changed

+83
-0
lines changed

‎packages/compiler-cli/src/ngtsc/diagnostics/src/error_code.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ export enum ErrorCode {
122122
*/
123123
WRITE_TO_READ_ONLY_VARIABLE = 8005,
124124

125+
/**
126+
* A template variable was declared twice. For example:
127+
*
128+
* ```html
129+
* <div *ngFor="let i of items; let i = index">
130+
* </div>
131+
* ```
132+
*/
133+
DUPLICATE_VARIABLE_DECLARATION = 8006,
134+
125135
/**
126136
* An injectable already has a `ɵprov` property.
127137
*/

‎packages/compiler-cli/src/ngtsc/typecheck/src/oob.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@ export interface OutOfBandDiagnosticRecorder {
5050

5151
illegalAssignmentToTemplateVar(
5252
templateId: TemplateId, assignment: PropertyWrite, target: TmplAstVariable): void;
53+
54+
/**
55+
* Reports a duplicate declaration of a template variable.
56+
*
57+
* @param templateId the template type-checking ID of the template which contains the duplicate
58+
* declaration.
59+
* @param variable the `TmplAstVariable` which duplicates a previously declared variable.
60+
* @param firstDecl the first variable declaration which uses the same name as `variable`.
61+
*/
62+
duplicateTemplateVar(
63+
templateId: TemplateId, variable: TmplAstVariable, firstDecl: TmplAstVariable): void;
5364
}
5465

5566
export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecorder {
@@ -100,4 +111,23 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
100111
span: target.valueSpan || target.sourceSpan,
101112
}));
102113
}
114+
115+
duplicateTemplateVar(
116+
templateId: TemplateId, variable: TmplAstVariable, firstDecl: TmplAstVariable): void {
117+
const mapping = this.resolver.getSourceMapping(templateId);
118+
const errorMsg =
119+
`Cannot redeclare variable '${variable.name}' as it was previously declared elsewhere for the same template.`;
120+
121+
// The allocation of the error here is pretty useless for variables declared in microsyntax,
122+
// since the sourceSpan refers to the entire microsyntax property, not a span for the specific
123+
// variable in question.
124+
//
125+
// TODO(alxhub): allocate to a tighter span once one is available.
126+
this._diagnostics.push(makeTemplateDiagnostic(
127+
mapping, variable.sourceSpan, ts.DiagnosticCategory.Error,
128+
ngErrorCode(ErrorCode.DUPLICATE_VARIABLE_DECLARATION), errorMsg, {
129+
text: `The variable '${firstDecl.name}' was first declared here.`,
130+
span: firstDecl.sourceSpan,
131+
}));
132+
}
103133
}

‎packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,17 @@ class Scope {
738738
// has.
739739
if (templateOrNodes instanceof TmplAstTemplate) {
740740
// The template's variable declarations need to be added as `TcbVariableOp`s.
741+
const varMap = new Map<string, TmplAstVariable>();
742+
741743
for (const v of templateOrNodes.variables) {
744+
// Validate that variables on the `TmplAstTemplate` are only declared once.
745+
if (!varMap.has(v.name)) {
746+
varMap.set(v.name, v);
747+
} else {
748+
const firstDecl = varMap.get(v.name) !;
749+
tcb.oobRecorder.duplicateTemplateVar(tcb.id, v, firstDecl);
750+
}
751+
742752
const opIndex = scope.opQueue.push(new TcbVariableOp(tcb, scope, templateOrNodes, v)) - 1;
743753
scope.varMap.set(v, opIndex);
744754
}

‎packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,4 +400,5 @@ export class NoopOobRecorder implements OutOfBandDiagnosticRecorder {
400400
missingReferenceTarget(): void {}
401401
missingPipe(): void {}
402402
illegalAssignmentToTemplateVar(): void {}
403+
duplicateTemplateVar(): void {}
403404
}

‎packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import * as ts from 'typescript';
1010

11+
import {ErrorCode, ngErrorCode} from '../../src/ngtsc/diagnostics';
1112
import {absoluteFrom as _, getFileSystem} from '../../src/ngtsc/file_system';
1213
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
1314
import {loadStandardTestFiles} from '../helpers/src/mock_file_loading';
@@ -1303,6 +1304,36 @@ export declare class AnimationEvent {
13031304
expect(getSourceCodeForDiagnostic(diags[0])).toEqual('y = !y');
13041305
});
13051306

1307+
it('should detect a duplicate variable declaration', () => {
1308+
env.write('test.ts', `
1309+
import {Component, NgModule} from '@angular/core';
1310+
import {CommonModule} from '@angular/common';
1311+
1312+
@Component({
1313+
selector: 'test',
1314+
template: \`
1315+
<div *ngFor="let i of items; let i = index">
1316+
{{i}}
1317+
</div>
1318+
\`,
1319+
})
1320+
export class TestCmp {
1321+
items!: string[];
1322+
}
1323+
1324+
@NgModule({
1325+
declarations: [TestCmp],
1326+
imports: [CommonModule],
1327+
})
1328+
export class Module {}
1329+
`);
1330+
1331+
const diags = env.driveDiagnostics();
1332+
expect(diags.length).toEqual(1);
1333+
expect(diags[0].code).toEqual(ngErrorCode(ErrorCode.DUPLICATE_VARIABLE_DECLARATION));
1334+
expect(getSourceCodeForDiagnostic(diags[0])).toContain('let i of items;');
1335+
});
1336+
13061337
it('should still type-check when fileToModuleName aliasing is enabled, but alias exports are not in the .d.ts file',
13071338
() => {
13081339
// The template type-checking file imports directives/pipes in order to type-check their

‎tools/public_api_guard/error_code.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,6 @@ export declare enum ErrorCode {
3030
MISSING_REFERENCE_TARGET = 8003,
3131
MISSING_PIPE = 8004,
3232
WRITE_TO_READ_ONLY_VARIABLE = 8005,
33+
DUPLICATE_VARIABLE_DECLARATION = 8006,
3334
INJECTABLE_DUPLICATE_PROV = 9001
3435
}

0 commit comments

Comments
 (0)