diff --git a/modules/@angular/language-service/src/typescript_host.ts b/modules/@angular/language-service/src/typescript_host.ts index 66f413193f..f47ef5df44 100644 --- a/modules/@angular/language-service/src/typescript_host.ts +++ b/modules/@angular/language-service/src/typescript_host.ts @@ -571,25 +571,7 @@ class TypeScriptSymbolQuery implements SymbolQuery { private program: ts.Program, private checker: ts.TypeChecker, private source: ts.SourceFile, private fetchPipes: () => SymbolTable) {} - getTypeKind(symbol: Symbol): BuiltinType { - const type = this.getTsTypeOf(symbol); - if (type) { - if (type.flags & ts.TypeFlags.Any) { - return BuiltinType.Any; - } else if ( - type.flags & - (ts.TypeFlags.String | ts.TypeFlags.StringLike | ts.TypeFlags.StringLiteral)) { - return BuiltinType.String; - } else if (type.flags & (ts.TypeFlags.Number | ts.TypeFlags.NumberLike)) { - return BuiltinType.Number; - } else if (type.flags & (ts.TypeFlags.Undefined)) { - return BuiltinType.Undefined; - } else if (type.flags & (ts.TypeFlags.Null)) { - return BuiltinType.Null; - } - } - return BuiltinType.Other; - } + getTypeKind(symbol: Symbol): BuiltinType { return typeKindOf(this.getTsTypeOf(symbol)); } getBuiltinType(kind: BuiltinType): Symbol { // TODO: Replace with typeChecker API when available. @@ -1223,4 +1205,35 @@ function getTypeParameterOf(type: ts.Type, name: string): ts.Type { return typeArguments[0]; } } +} + +function typeKindOf(type: ts.Type): BuiltinType { + if (type) { + if (type.flags & ts.TypeFlags.Any) { + return BuiltinType.Any; + } else if ( + type.flags & (ts.TypeFlags.String | ts.TypeFlags.StringLike | ts.TypeFlags.StringLiteral)) { + return BuiltinType.String; + } else if (type.flags & (ts.TypeFlags.Number | ts.TypeFlags.NumberLike)) { + return BuiltinType.Number; + } else if (type.flags & (ts.TypeFlags.Undefined)) { + return BuiltinType.Undefined; + } else if (type.flags & (ts.TypeFlags.Null)) { + return BuiltinType.Null; + } else if (type.flags & ts.TypeFlags.Union) { + // If all the constituent types of a union are the same kind, it is also that kind. + let candidate: BuiltinType; + const unionType = type as ts.UnionType; + if (unionType.types.length > 0) { + candidate = typeKindOf(unionType.types[0]); + for (const subType of unionType.types) { + if (candidate != typeKindOf(subType)) { + return BuiltinType.Other; + } + } + } + return candidate; + } + } + return BuiltinType.Other; } \ No newline at end of file diff --git a/modules/@angular/language-service/test/diagnostics_spec.ts b/modules/@angular/language-service/test/diagnostics_spec.ts index effcdae7b0..8327449c66 100644 --- a/modules/@angular/language-service/test/diagnostics_spec.ts +++ b/modules/@angular/language-service/test/diagnostics_spec.ts @@ -121,6 +121,15 @@ describe('diagnostics', () => { code, fileName => { expect(() => ngService.getDiagnostics(fileName)).not.toThrow(); }); }); + it('should not report an error for sub-types of string', () => { + const code = + ` @Component({template: \`
\`}) export class MyComponent { something: 'foo' | 'bar'; }`; + addCode(code, fileName => { + const diagnostics = ngService.getDiagnostics(fileName); + onlyModuleDiagnostics(diagnostics); + }); + }); + function addCode(code: string, cb: (fileName: string, content?: string) => void) { const fileName = '/app/app.component.ts'; const originalContent = mockHost.getFileContent(fileName); @@ -137,6 +146,13 @@ describe('diagnostics', () => { function onlyModuleDiagnostics(diagnostics: Diagnostics) { // Expect only the 'MyComponent' diagnostic expect(diagnostics.length).toBe(1); + if (diagnostics.length > 1) { + for (const diagnostic of diagnostics) { + if (diagnostic.message.indexOf('MyComponent') >= 0) continue; + console.error(`(${diagnostic.span.start}:${diagnostic.span.end}): ${diagnostic.message}`); + } + return; + } expect(diagnostics[0].message.indexOf('MyComponent') >= 0).toBeTruthy(); } });