fix(language-service): infer correct type of `?.` expressions

Fixes #15885
This commit is contained in:
Chuck Jazdzewski 2017-04-10 15:10:34 -07:00 committed by Tobias Bosch
parent 5a88d2f68b
commit 0a3a9afe58
3 changed files with 53 additions and 19 deletions

View File

@ -649,8 +649,14 @@ class TypeScriptSymbolQuery implements SymbolQuery {
}
getNonNullableType(symbol: Symbol): Symbol {
// TODO: Replace with typeChecker API when available;
return symbol;
if (symbol instanceof TypeWrapper && (typeof this.checker.getNonNullableType == 'function')) {
const tsType = symbol.tsType;
const nonNullableType = this.checker.getNonNullableType(tsType);
if (nonNullableType != tsType) {
return new TypeWrapper(nonNullableType, symbol.context);
}
}
return this.getBuiltinType(BuiltinType.Any);
}
getPipes(): SymbolTable {

View File

@ -95,7 +95,7 @@ describe('diagnostics', () => {
const code = '\n@Component({template: \'<form></form>\'}) export class MyComponent {}';
addCode(code, (fileName, content) => {
const diagnostics = ngService.getDiagnostics(fileName);
onlyModuleDiagnostics(diagnostics !);
expectOnlyModuleDiagnostics(diagnostics !);
});
});
@ -134,7 +134,7 @@ describe('diagnostics', () => {
` @Component({template: \`<div *ngIf="something === 'foo'"></div>\`}) export class MyComponent { something: 'foo' | 'bar'; }`;
addCode(code, fileName => {
const diagnostics = ngService.getDiagnostics(fileName);
onlyModuleDiagnostics(diagnostics !);
expectOnlyModuleDiagnostics(diagnostics !);
});
});
@ -155,7 +155,7 @@ describe('diagnostics', () => {
` @Component({template: \`<div *ngIf="something === undefined"></div>\`}) export class MyComponent { something = 'foo'; }})`;
addCode(code, fileName => {
const diagnostics = ngService.getDiagnostics(fileName);
onlyModuleDiagnostics(diagnostics !);
expectOnlyModuleDiagnostics(diagnostics !);
});
});
@ -233,7 +233,7 @@ describe('diagnostics', () => {
})
export class MyComponent {}
`,
fileName => onlyModuleDiagnostics(ngService.getDiagnostics(fileName)));
fileName => expectOnlyModuleDiagnostics(ngService.getDiagnostics(fileName)));
});
// Issue #15625
@ -254,10 +254,33 @@ describe('diagnostics', () => {
`,
fileName => {
const diagnostics = ngService.getDiagnostics(fileName);
onlyModuleDiagnostics(diagnostics);
expectOnlyModuleDiagnostics(diagnostics);
});
});
// Issue #15885
it('should be able to remove null and undefined from a type', () => {
mockHost.overrideOptions(options => {
options.strictNullChecks = true;
return options;
});
addCode(
`
@Component({
selector: 'my-component',
template: \` {{test?.a}}
\`
})
export class MyComponent {
test: {a: number, b: number} | null = {
a: 1,
b: 2
};
}
`,
fileName => expectOnlyModuleDiagnostics(ngService.getDiagnostics(fileName)));
});
function addCode(code: string, cb: (fileName: string, content?: string) => void) {
const fileName = '/app/app.component.ts';
const originalContent = mockHost.getFileContent(fileName);
@ -271,7 +294,7 @@ describe('diagnostics', () => {
}
}
function onlyModuleDiagnostics(diagnostics: Diagnostics) {
function expectOnlyModuleDiagnostics(diagnostics: Diagnostics) {
// Expect only the 'MyComponent' diagnostic
expect(diagnostics.length).toBe(1);
if (diagnostics.length > 1) {

View File

@ -68,6 +68,7 @@ export class MockTypescriptHost implements ts.LanguageServiceHost {
private scriptVersion = new Map<string, number>();
private overrides = new Map<string, string>();
private projectVersion = 0;
private options: ts.CompilerOptions;
constructor(private scriptNames: string[], private data: MockData) {
const moduleFilename = module.filename.replace(/\\/g, '/');
@ -77,6 +78,16 @@ export class MockTypescriptHost implements ts.LanguageServiceHost {
let distIndex = moduleFilename.indexOf('/dist/all');
if (distIndex >= 0)
this.nodeModulesPath = path.join(moduleFilename.substr(0, distIndex), 'node_modules');
this.options = {
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.CommonJS,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
emitDecoratorMetadata: true,
experimentalDecorators: true,
removeComments: false,
noImplicitAny: false,
lib: ['lib.es2015.d.ts', 'lib.dom.d.ts'],
};
}
override(fileName: string, content: string) {
@ -99,19 +110,13 @@ export class MockTypescriptHost implements ts.LanguageServiceHost {
forgetAngular() { this.angularPath = undefined; }
getCompilationSettings(): ts.CompilerOptions {
return {
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.CommonJS,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
emitDecoratorMetadata: true,
experimentalDecorators: true,
removeComments: false,
noImplicitAny: false,
lib: ['lib.es2015.d.ts', 'lib.dom.d.ts'],
};
overrideOptions(cb: (options: ts.CompilerOptions) => ts.CompilerOptions) {
this.options = cb(this.options);
this.projectVersion++;
}
getCompilationSettings(): ts.CompilerOptions { return this.options; }
getProjectVersion(): string { return this.projectVersion.toString(); }
getScriptFileNames(): string[] { return this.scriptNames; }