refactor(core): static-query schematic should handle function callbacks (#29663)

Currently the static-query schematic is not able to properly handle
call expressions that pass function declarations that access a given
query. e.g.

```ts
ngOnInit() {
  this._callFunction(() => this.myQuery.doSomething());
}

_callFunction(cb: any) { cb(); }
```

In that case the passed function is executed synchronously in
the "ngOnInit" lifecycle and therefore the query needs to be
detected as "static".

We can fix this by keeping track of the current function context
and using it to resolve identifiers to the passed arguments.

PR Close #29663
This commit is contained in:
Paul Gschwendtner
2019-04-08 15:54:08 +02:00
committed by Igor Minar
parent 00bf636afa
commit 1102b02406
2 changed files with 206 additions and 39 deletions

View File

@ -639,6 +639,93 @@ describe('static-queries migration', () => {
.toContain(`@${queryType}('test', { static: true }) query2: any;`);
});
it('should handle function callbacks which statically access queries', () => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@Component({template: '<span #test></span>'})
export class MyComp {
private @${queryType}('test') query: any;
ngOnInit() {
this.callSync(() => this.query.doSomething());
}
callSync(cb: Function) {
this.callSync2(cb);
}
callSync2(cb: Function) {
cb();
}
}
`);
runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should handle class instantiations with specified callbacks that access queries', () => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
import {External} from './external';
@Component({template: '<span #test></span>'})
export class MyComp {
private @${queryType}('test') query: any;
ngOnInit() {
new External(() => this.query.doSomething());
}
}
`);
writeFile('/external.ts', `
export class External {
constructor(cb: () => void) {
// Add extra parentheses to ensure that expression is unwrapped.
((cb))();
}
}
`);
runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should handle nested functions with arguments from parent closure', () => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@Component({template: '<span #test></span>'})
export class MyComp {
private @${queryType}('test') query: any;
ngOnInit() {
this.callSync(() => this.query.doSomething());
}
callSync(cb: Function) {
function callSyncNested() {
// The "cb" identifier comes from the "callSync" function.
cb();
}
callSyncNested();
}
}
`);
runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should not mark queries used in setTimeout as static', () => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';