fix(compiler): preserve this.$event and this.$any accesses in expressions (#39323)
Currently expressions `$event.foo()` and `this.$event.foo()`, as well as `$any(foo)` and `this.$any(foo)`, are treated as the same expression by the compiler, because `this` is considered the same implicit receiver as when the receiver is omitted. This introduces the following issues: 1. Any time something called `$any` is used, it'll be stripped away, leaving only the first parameter. 2. If something called `$event` is used anywhere in a template, it'll be preserved as `$event`, rather than being rewritten to `ctx.$event`, causing the value to undefined at runtime. This applies to listener, property and text bindings. These changes resolve the first issue and part of the second one by preserving anything that is accessed through `this`, even if it's one of the "special" ones like `$any` or `$event`. Furthermore, these changes only expose the `$event` global variable inside event listeners, whereas previously it was available everywhere. Fixes #30278. PR Close #39323
This commit is contained in:
committed by
Joey Perrott
parent
4e68254514
commit
cbc0907bfd
@@ -447,4 +447,137 @@ describe('compiler compliance: listen()', () => {
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect host bindings');
|
||||
});
|
||||
|
||||
it('should assume $event is referring to the event variable in a listener by default', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: '<div (click)="c($event)"></div>'
|
||||
})
|
||||
class Comp {
|
||||
c(event: MouseEvent) {}
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
i0.ɵɵlistener("click", function Comp_Template_div_click_0_listener($event) { return ctx.c($event); });
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect event listener');
|
||||
});
|
||||
|
||||
it('should preserve accesses to $event if it is done through `this` in a listener', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: '<div (click)="c(this.$event)"></div>'
|
||||
})
|
||||
class Comp {
|
||||
$event = {};
|
||||
c(value: {}) {}
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
i0.ɵɵlistener("click", function Comp_Template_div_click_0_listener() { return ctx.c(ctx.$event); });
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect event listener');
|
||||
});
|
||||
|
||||
it('should not assume that $event is referring to an event object inside a property', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: '<div [event]="$event"></div>'
|
||||
})
|
||||
class Comp {
|
||||
$event = 1;
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
i0.ɵɵproperty("event", ctx.$event);
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect property binding');
|
||||
});
|
||||
|
||||
it('should assume $event is referring to the event variable in a listener by default inside a host binding',
|
||||
() => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
host: {
|
||||
'(click)': 'c($event)'
|
||||
}
|
||||
})
|
||||
class Dir {
|
||||
c(event: MouseEvent) {}
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
i0.ɵɵlistener("click", function Dir_click_HostBindingHandler($event) { return ctx.c($event); });
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect event listener');
|
||||
});
|
||||
|
||||
it('should preserve accesses to $event if it is done through `this` in a listener inside a host binding',
|
||||
() => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
host: {
|
||||
'(click)': 'c(this.$event)'
|
||||
}
|
||||
})
|
||||
class Dir {
|
||||
$event = {};
|
||||
c(value: {}) {}
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
i0.ɵɵlistener("click", function Dir_click_HostBindingHandler() { return ctx.c(ctx.$event); });
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect event listener');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -191,4 +191,57 @@ describe('r3_view_compiler', () => {
|
||||
expectEmit(result.source, template, 'Incorrect initialization attributes');
|
||||
});
|
||||
});
|
||||
|
||||
describe('$any', () => {
|
||||
it('should strip out $any wrappers', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: '<div [tabIndex]="$any(10)"></div>'
|
||||
})
|
||||
class Comp {
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
i0.ɵɵproperty("tabIndex", 10);
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should preserve $any if it is accessed through `this`', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: '<div [tabIndex]="this.$any(null)"></div>'
|
||||
})
|
||||
class Comp {
|
||||
$any(value: null): any {
|
||||
return value as any;
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
i0.ɵɵproperty("tabIndex", ctx.$any(null));
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user