feat(compiler): support safe keyed read expressions (#41911)

Currently we support safe property (`a?.b`) and method (`a?.b()`) accesses, but we don't handle safe keyed reads (`a?.[0]`) which is inconsistent. These changes expand the compiler in order to support safe key read expressions as well.

PR Close #41911
This commit is contained in:
Kristiyan Kostadinov
2021-05-01 18:46:34 +02:00
committed by Jessica Janiuk
parent 47270d9e63
commit ba084857ea
21 changed files with 385 additions and 50 deletions
@@ -0,0 +1,65 @@
/****************************************************************************************************
* PARTIAL FILE: safe_keyed_read.js
****************************************************************************************************/
import { Component, NgModule } from '@angular/core';
import * as i0 from "@angular/core";
export class MyApp {
constructor() {
this.unknownNames = null;
this.knownNames = [['Frodo', 'Bilbo']];
this.species = null;
this.keys = null;
this.speciesMap = { key: 'unknown' };
}
}
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
<span [title]="'Your last name is ' + (unknownNames?.[0] || 'unknown')">
Hello, {{ knownNames?.[0]?.[1] }}!
You are a Balrog: {{ species?.[0]?.[1]?.[2]?.[3]?.[4]?.[5] || 'unknown' }}
You are an Elf: {{ speciesMap?.[keys?.[0] ?? 'key'] }}
You are an Orc: {{ speciesMap?.['key'] }}
</span>
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
<span [title]="'Your last name is ' + (unknownNames?.[0] || 'unknown')">
Hello, {{ knownNames?.[0]?.[1] }}!
You are a Balrog: {{ species?.[0]?.[1]?.[2]?.[3]?.[4]?.[5] || 'unknown' }}
You are an Elf: {{ speciesMap?.[keys?.[0] ?? 'key'] }}
You are an Orc: {{ speciesMap?.['key'] }}
</span>
`
}]
}] });
export class MyModule {
}
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyApp] });
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, decorators: [{
type: NgModule,
args: [{ declarations: [MyApp] }]
}] });
/****************************************************************************************************
* PARTIAL FILE: safe_keyed_read.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
unknownNames: string[] | null;
knownNames: string[][];
species: null;
keys: null;
speciesMap: Record<string, string>;
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never>;
}
export declare class MyModule {
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof MyApp], never, never>;
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
}
@@ -0,0 +1,22 @@
{
"$schema": "../../test_case_schema.json",
"cases": [
{
"description": "should handle safe keyed reads inside templates",
"inputFiles": [
"safe_keyed_read.ts"
],
"expectations": [
{
"files": [
{
"expected": "safe_keyed_read_template.js",
"generated": "safe_keyed_read.js"
}
],
"failureMessage": "Incorrect template"
}
]
}
]
}
@@ -0,0 +1,23 @@
import {Component, NgModule} from '@angular/core';
@Component({
template: `
<span [title]="'Your last name is ' + (unknownNames?.[0] || 'unknown')">
Hello, {{ knownNames?.[0]?.[1] }}!
You are a Balrog: {{ species?.[0]?.[1]?.[2]?.[3]?.[4]?.[5] || 'unknown' }}
You are an Elf: {{ speciesMap?.[keys?.[0] ?? 'key'] }}
You are an Orc: {{ speciesMap?.['key'] }}
</span>
`
})
export class MyApp {
unknownNames: string[]|null = null;
knownNames: string[][] = [['Frodo', 'Bilbo']];
species = null;
keys = null;
speciesMap: Record<string, string> = {key: 'unknown'};
}
@NgModule({declarations: [MyApp]})
export class MyModule {
}
@@ -0,0 +1,13 @@
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
i0.ɵɵelementStart(0, "span", 0);
i0.ɵɵtext(1);
i0.ɵɵelementEnd();
}
if (rf & 2) {
let $tmp_0_0$;
i0.ɵɵproperty("title", "Your last name is " + ((ctx.unknownNames == null ? null : ctx.unknownNames[0]) || "unknown"));
i0.ɵɵadvance(1);
i0.ɵɵtextInterpolate4(" Hello, ", ctx.knownNames == null ? null : ctx.knownNames[0] == null ? null : ctx.knownNames[0][1], "! You are a Balrog: ", (ctx.species == null ? null : ctx.species[0] == null ? null : ctx.species[0][1] == null ? null : ctx.species[0][1][2] == null ? null : ctx.species[0][1][2][3] == null ? null : ctx.species[0][1][2][3][4] == null ? null : ctx.species[0][1][2][3][4][5]) || "unknown", " You are an Elf: ", ctx.speciesMap == null ? null : ctx.speciesMap[($tmp_0_0$ = ctx.keys == null ? null : ctx.keys[0]) !== null && $tmp_0_0$ !== undefined ? $tmp_0_0$ : "key"], " You are an Orc: ", ctx.speciesMap == null ? null : ctx.speciesMap["key"], " ");
}
}