fix(compiler-cli): resolve type of exported *ngIf variable. (#33016)

Currently, method `getVarDeclarations()` does not try to resolve the type of
exported variable from *ngIf directive. It always returns `any` type.
By resolving the real type of exported variable, it is now possible to use this
type information in language service and provide completions, go to definition
and quick info functionality in expressions that use exported variable.
Also language service will provide more accurate diagnostic errors during
development.

PR Close #33016
This commit is contained in:
Andrius 2019-10-05 15:18:05 +03:00 committed by Miško Hevery
parent 5557dec120
commit 39587ad127
3 changed files with 39 additions and 0 deletions

View File

@ -154,6 +154,19 @@ function refinedVariableType(
}
}
// Special case the ngIf directive ( *ngIf="data$ | async as variable" )
const ngIfDirective =
templateElement.directives.find(d => identifierName(d.directive.type) === 'NgIf');
if (ngIfDirective) {
const ngIfBinding = ngIfDirective.inputs.find(i => i.directiveName === 'ngIf');
if (ngIfBinding) {
const bindingType = new AstType(info.members, info.query, {}).getType(ngIfBinding.value);
if (bindingType) {
return bindingType;
}
}
}
// We can't do better, return any
return info.query.getBuiltinType(BuiltinType.Any);
}

View File

@ -130,6 +130,18 @@ describe('expression diagnostics', () => {
</div>
`,
'Identifier \'nume\' is not defined'));
it('should accept an async *ngIf', () => accept(`
<div *ngIf="promised_person | async as p">
{{p.name.first}} {{p.name.last}}
</div>
`));
it('should reject misspelled field in async *ngIf', () => reject(
`
<div *ngIf="promised_person | async as p">
{{p.name.first}} {{p.nume.last}}
</div>
`,
'Identifier \'nume\' is not defined'));
it('should reject access to potentially undefined field',
() => reject(`<div>{{maybe_person.name.first}}`, 'The expression might be null'));
it('should accept a safe accss to an undefined field',

View File

@ -55,6 +55,20 @@ describe('completions', () => {
expectContains(fileName, 'name', 'name', 'street');
});
it('should be able to get completions for exported *ngIf variable', () => {
const fileName = mockHost.addCode(`
interface Person {
name: string,
street: string
}
@Component({template: '<div *ngIf="promised_person | async as person">{{person.~{name}name}}</div'})
export class MyComponent {
promised_person: Promise<Person>
}`);
expectContains(fileName, 'name', 'name', 'street');
});
it('should be able to infer the type of a ngForOf with an async pipe', () => {
const fileName = mockHost.addCode(`
interface Person {