| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							|  |  |  |  * Copyright Google Inc. All Rights Reserved. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Use of this source code is governed by an MIT-style license that can be | 
					
						
							|  |  |  |  * found in the LICENSE file at https://angular.io/license
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import * as ts from 'typescript'; | 
					
						
							|  |  |  | import {createLanguageService} from '../src/language_service'; | 
					
						
							|  |  |  | import {TypeScriptServiceHost} from '../src/typescript_host'; | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  | import {MockTypescriptHost} from './test_utils'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Note: If we want to test that a specific diagnostic message is emitted, then | 
					
						
							| 
									
										
										
										
											2019-09-10 11:21:12 -05:00
										 |  |  |  * use the `mockHost.addCode()` helper method to add code to an existing file and check | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |  * that the diagnostic messages contain the expected output. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * If the goal is to assert that there is no error in a specific file, then use | 
					
						
							|  |  |  |  * `mockHost.override()` method to completely override an existing file, and | 
					
						
							|  |  |  |  * make sure no diagnostics are produced. When doing so, be extra cautious | 
					
						
							|  |  |  |  * about import statements and make sure to assert empty TS diagnostic messages | 
					
						
							|  |  |  |  * as well. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-17 18:42:27 -07:00
										 |  |  | const TEST_TEMPLATE = '/app/test.ng'; | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  | const APP_COMPONENT = '/app/app.component.ts'; | 
					
						
							| 
									
										
										
										
											2019-10-14 15:24:04 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | describe('diagnostics', () => { | 
					
						
							| 
									
										
										
										
											2019-10-16 12:00:41 -07:00
										 |  |  |   const mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts']); | 
					
						
							|  |  |  |   const tsLS = ts.createLanguageService(mockHost); | 
					
						
							|  |  |  |   const ngHost = new TypeScriptServiceHost(mockHost, tsLS); | 
					
						
							|  |  |  |   const ngLS = createLanguageService(ngHost); | 
					
						
							| 
									
										
										
										
											2018-07-06 09:13:25 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |   beforeEach(() => { | 
					
						
							|  |  |  |     mockHost.reset(); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   it('should produce no diagnostics for test.ng', () => { | 
					
						
							|  |  |  |     // there should not be any errors on existing external template
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     expect(ngLS.getSemanticDiagnostics('/app/test.ng')).toEqual([]); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   it('should not return TS and NG errors for existing files', () => { | 
					
						
							|  |  |  |     const files = [ | 
					
						
							|  |  |  |       '/app/app.component.ts', | 
					
						
							|  |  |  |       '/app/main.ts', | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  |     for (const file of files) { | 
					
						
							|  |  |  |       const syntaxDiags = tsLS.getSyntacticDiagnostics(file); | 
					
						
							|  |  |  |       expect(syntaxDiags).toEqual([]); | 
					
						
							|  |  |  |       const semanticDiags = tsLS.getSemanticDiagnostics(file); | 
					
						
							|  |  |  |       expect(semanticDiags).toEqual([]); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |       const ngDiags = ngLS.getSemanticDiagnostics(file); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       expect(ngDiags).toEqual([]); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-31 16:15:11 -07:00
										 |  |  |   it('should report error for unexpected end of expression', () => { | 
					
						
							|  |  |  |     const content = mockHost.override(TEST_TEMPLATE, `{{ 5 / }}`); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-10-31 16:15:11 -07:00
										 |  |  |     expect(diags.length).toBe(1); | 
					
						
							|  |  |  |     const {messageText, start, length} = diags[0]; | 
					
						
							|  |  |  |     expect(messageText) | 
					
						
							|  |  |  |         .toBe( | 
					
						
							|  |  |  |             'Parser Error: Unexpected end of expression: {{ 5 / }} ' + | 
					
						
							|  |  |  |             'at the end of the expression [{{ 5 / }}] in /app/test.ng@0:0'); | 
					
						
							|  |  |  |     expect(start).toBe(0); | 
					
						
							|  |  |  |     expect(length).toBe(content.length); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-17 18:42:27 -07:00
										 |  |  |   // https://github.com/angular/vscode-ng-language-service/issues/242
 | 
					
						
							|  |  |  |   it('should support $any() type cast function', () => { | 
					
						
							|  |  |  |     mockHost.override(TEST_TEMPLATE, `<div>{{$any(title).xyz}}</div>`); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-10-17 18:42:27 -07:00
										 |  |  |     expect(diags).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should report error for $any() with incorrect number of arguments', () => { | 
					
						
							|  |  |  |     const templates = [ | 
					
						
							|  |  |  |       '<div>{{$any().xyz}}</div>',              // no argument
 | 
					
						
							|  |  |  |       '<div>{{$any(title, title).xyz}}</div>',  // two arguments
 | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  |     for (const template of templates) { | 
					
						
							|  |  |  |       mockHost.override(TEST_TEMPLATE, template); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |       const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-10-17 18:42:27 -07:00
										 |  |  |       expect(diags.length).toBe(1); | 
					
						
							| 
									
										
										
										
											2020-02-25 08:32:38 -08:00
										 |  |  |       expect(diags[0].messageText).toBe('Unable to resolve signature for call of $any'); | 
					
						
							| 
									
										
										
										
											2019-10-17 18:42:27 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-24 10:17:25 -07:00
										 |  |  |   it('should not produce diagnostics for slice pipe with arguments', () => { | 
					
						
							|  |  |  |     mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							|  |  |  |       <div *ngFor="let h of heroes | slice:0:1"> | 
					
						
							|  |  |  |         {{h.name}} | 
					
						
							|  |  |  |       </div>`);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-10-24 10:17:25 -07:00
										 |  |  |     expect(diags).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should produce diagnostics for slice pipe with args when member is invalid', () => { | 
					
						
							|  |  |  |     mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							|  |  |  |       <div *ngFor="let h of heroes | slice:0:1"> | 
					
						
							|  |  |  |         {{h.age}} | 
					
						
							|  |  |  |       </div>`);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-10-24 10:17:25 -07:00
										 |  |  |     expect(diags.length).toBe(1); | 
					
						
							|  |  |  |     expect(diags[0].messageText) | 
					
						
							|  |  |  |         .toBe(`Identifier 'age' is not defined. 'Hero' does not contain such a member`); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-12 17:40:56 -08:00
										 |  |  |   it('should not report error for variable initialized as class method', () => { | 
					
						
							|  |  |  |     mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							|  |  |  |       <ng-template let-greet="myClick"> | 
					
						
							|  |  |  |         <span (click)="greet()"></span> | 
					
						
							|  |  |  |       </ng-template> | 
					
						
							|  |  |  |     `);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const diagnostics = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-11-12 17:40:56 -08:00
										 |  |  |     expect(diagnostics).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-08 13:48:35 -07:00
										 |  |  |   describe('diagnostics for expression comparisons', () => { | 
					
						
							|  |  |  |     for (let [left, right, leftTy, rightTy] of [ | 
					
						
							|  |  |  |              ['\'abc\'', 1, 'string', 'number'], | 
					
						
							|  |  |  |              ['hero', 2, 'object', 'number'], | 
					
						
							|  |  |  |              ['strOrNumber', 'hero', 'string|number', 'object'], | 
					
						
							|  |  |  |     ]) { | 
					
						
							|  |  |  |       it(`it should report errors for mismtched types in a comparison: ${leftTy} and ${rightTy}`, | 
					
						
							|  |  |  |          () => { | 
					
						
							|  |  |  |            mockHost.override(TEST_TEMPLATE, `{{ ${left} != ${right} }}`); | 
					
						
							|  |  |  |            const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							|  |  |  |            expect(diags.length).toBe(1); | 
					
						
							|  |  |  |            expect(diags[0].messageText).toBe(`Expected operands to be of comparable types or any`); | 
					
						
							|  |  |  |          }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (let [left, right, leftTy, rightTy] of [ | 
					
						
							|  |  |  |              ['\'abc\'', 'anyValue', 'string', 'any'], | 
					
						
							|  |  |  |              ['\'abc\'', null, 'string', 'null'], | 
					
						
							|  |  |  |              ['\'abc\'', undefined, 'string', 'undefined'], | 
					
						
							|  |  |  |              [null, null, 'null', 'null'], | 
					
						
							|  |  |  |              ['{a: 1}', '{b: 2}', 'object', 'object'], | 
					
						
							|  |  |  |              ['strOrNumber', '1', 'string|number', 'number'], | 
					
						
							|  |  |  |     ]) { | 
					
						
							|  |  |  |       it(`it should not report errors for compatible types in a comparison: ${leftTy} and ${ | 
					
						
							|  |  |  |              rightTy}`,
 | 
					
						
							|  |  |  |          () => { | 
					
						
							|  |  |  |            mockHost.override(TEST_TEMPLATE, `{{ ${left} != ${right} }}`); | 
					
						
							|  |  |  |            const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							|  |  |  |            expect(diags.length).toBe(0); | 
					
						
							|  |  |  |          }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-27 11:29:37 -06:00
										 |  |  |   describe('diagnostics for ngFor exported values', () => { | 
					
						
							| 
									
										
										
										
											2020-04-08 13:48:35 -07:00
										 |  |  |     it('should report errors for mismatched exported types', () => { | 
					
						
							| 
									
										
										
										
											2019-11-27 11:29:37 -06:00
										 |  |  |       mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							|  |  |  |         <div *ngFor="let hero of heroes; let i = index; let isFirst = first"> | 
					
						
							|  |  |  |             'i' is a number; 'isFirst' is a boolean | 
					
						
							|  |  |  |           {{ i === isFirst }} | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |       `);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |       const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-11-27 11:29:37 -06:00
										 |  |  |       expect(diags.length).toBe(1); | 
					
						
							| 
									
										
										
										
											2020-04-08 13:48:35 -07:00
										 |  |  |       expect(diags[0].messageText).toBe(`Expected operands to be of comparable types or any`); | 
					
						
							| 
									
										
										
										
											2019-11-27 11:29:37 -06:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should not report errors for matching exported type', () => { | 
					
						
							|  |  |  |       mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							|  |  |  |         <div *ngFor="let hero of heroes; let i = index"> | 
					
						
							|  |  |  |             'i' is a number | 
					
						
							|  |  |  |           {{ i < 2 }} | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |       `);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |       const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-11-27 11:29:37 -06:00
										 |  |  |       expect(diags.length).toBe(0); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-08 13:42:05 -07:00
										 |  |  |   describe('diagnostics for ngIf exported values', () => { | 
					
						
							|  |  |  |     it('should infer the type of an implicit value in an NgIf context', () => { | 
					
						
							|  |  |  |       mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							|  |  |  |         <div *ngIf="title; let titleProxy;"> | 
					
						
							|  |  |  |             'titleProxy' is a string | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |           {{titleProxy.~{start-err}notAProperty~{end-err}}} | 
					
						
							| 
									
										
										
										
											2020-03-08 13:42:05 -07:00
										 |  |  |         </div> | 
					
						
							|  |  |  |       `);
 | 
					
						
							|  |  |  |       const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							|  |  |  |       expect(diags.length).toBe(1); | 
					
						
							|  |  |  |       const {messageText, start, length} = diags[0]; | 
					
						
							|  |  |  |       expect(messageText) | 
					
						
							|  |  |  |           .toBe( | 
					
						
							|  |  |  |               `Identifier 'notAProperty' is not defined. 'string' does not contain such a member`); | 
					
						
							|  |  |  |       const span = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'err'); | 
					
						
							|  |  |  |       expect(start).toBe(span.start); | 
					
						
							|  |  |  |       expect(length).toBe(span.length); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should infer the type of an ngIf value in an NgIf context', () => { | 
					
						
							|  |  |  |       mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							|  |  |  |         <div *ngIf="title as titleProxy"> | 
					
						
							|  |  |  |             'titleProxy' is a string | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |           {{titleProxy.~{start-err}notAProperty~{end-err}}} | 
					
						
							| 
									
										
										
										
											2020-03-08 13:42:05 -07:00
										 |  |  |         </div> | 
					
						
							|  |  |  |       `);
 | 
					
						
							|  |  |  |       const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							|  |  |  |       expect(diags.length).toBe(1); | 
					
						
							|  |  |  |       const {messageText, start, length} = diags[0]; | 
					
						
							|  |  |  |       expect(messageText) | 
					
						
							|  |  |  |           .toBe( | 
					
						
							|  |  |  |               `Identifier 'notAProperty' is not defined. 'string' does not contain such a member`); | 
					
						
							|  |  |  |       const span = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'err'); | 
					
						
							|  |  |  |       expect(start).toBe(span.start); | 
					
						
							|  |  |  |       expect(length).toBe(span.length); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-17 15:06:41 -06:00
										 |  |  |   describe('diagnostics for invalid indexed type property access', () => { | 
					
						
							|  |  |  |     it('should work with numeric index signatures (arrays)', () => { | 
					
						
							|  |  |  |       mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							| 
									
										
										
										
											2019-11-12 15:57:44 -06:00
										 |  |  |         {{heroes[0].badProperty}}`);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |       const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-11-17 15:06:41 -06:00
										 |  |  |       expect(diags.length).toBe(1); | 
					
						
							|  |  |  |       expect(diags[0].messageText) | 
					
						
							|  |  |  |           .toBe(`Identifier 'badProperty' is not defined. 'Hero' does not contain such a member`); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     describe('with string index signatures', () => { | 
					
						
							|  |  |  |       it('should work with index notation', () => { | 
					
						
							|  |  |  |         mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							|  |  |  |         {{heroesByName['Jacky'].badProperty}}`);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |         const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-11-17 15:06:41 -06:00
										 |  |  |         expect(diags.length).toBe(1); | 
					
						
							|  |  |  |         expect(diags[0].messageText) | 
					
						
							|  |  |  |             .toBe(`Identifier 'badProperty' is not defined. 'Hero' does not contain such a member`); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should work with dot notation', () => { | 
					
						
							|  |  |  |         mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							|  |  |  |         {{heroesByName.jacky.badProperty}}`);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |         const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-11-17 15:06:41 -06:00
										 |  |  |         expect(diags.length).toBe(1); | 
					
						
							|  |  |  |         expect(diags[0].messageText) | 
					
						
							|  |  |  |             .toBe(`Identifier 'badProperty' is not defined. 'Hero' does not contain such a member`); | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2019-12-02 15:22:07 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-03 10:45:17 +08:00
										 |  |  |       it('should not produce errors with dot notation if stringIndexType is a primitive type', | 
					
						
							| 
									
										
										
										
											2019-12-02 15:22:07 +08:00
										 |  |  |          () => { | 
					
						
							| 
									
										
										
										
											2019-12-03 10:45:17 +08:00
										 |  |  |            mockHost.override(TEST_TEMPLATE, `{{primitiveIndexType.test}}`); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |            const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-12-02 15:22:07 +08:00
										 |  |  |            expect(diags.length).toBe(0); | 
					
						
							|  |  |  |          }); | 
					
						
							| 
									
										
										
										
											2019-11-17 15:06:41 -06:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-11-12 15:57:44 -06:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-20 07:23:45 +00:00
										 |  |  |   it('should produce diagnostics for invalid tuple type property access', () => { | 
					
						
							|  |  |  |     mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							|  |  |  |         {{tupleArray[1].badProperty}}`);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-11-20 07:23:45 +00:00
										 |  |  |     expect(diags.length).toBe(1); | 
					
						
							|  |  |  |     expect(diags[0].messageText) | 
					
						
							|  |  |  |         .toBe(`Identifier 'badProperty' is not defined. 'Hero' does not contain such a member`); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-27 03:11:28 +00:00
										 |  |  |   it('should not produce errors if tuple array index out of bound', () => { | 
					
						
							|  |  |  |     mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							|  |  |  |         {{tupleArray[2].badProperty}}`);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-11-27 03:11:28 +00:00
										 |  |  |     expect(diags).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-25 14:23:32 -08:00
										 |  |  |   it('should not produce errors on function.bind()', () => { | 
					
						
							|  |  |  |     mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							|  |  |  |       <test-comp (test)="myClick.bind(this)"> | 
					
						
							|  |  |  |       </test-comp>`); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-11-25 14:23:32 -08:00
										 |  |  |     expect(diags).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 16:14:09 -07:00
										 |  |  |   it('should report access to an unknown field', () => { | 
					
						
							|  |  |  |     mockHost.override(TEST_TEMPLATE, `{{ foo }}`); | 
					
						
							|  |  |  |     const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE).map(d => d.messageText); | 
					
						
							|  |  |  |     expect(diags).toContain( | 
					
						
							|  |  |  |         `Identifier 'foo' is not defined. ` + | 
					
						
							|  |  |  |         `The component declaration, template variable declarations, ` + | 
					
						
							|  |  |  |         `and element references do not contain such a member`); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2019-10-14 15:24:04 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 16:14:09 -07:00
										 |  |  |   it('should report access to an unknown sub-field', () => { | 
					
						
							|  |  |  |     mockHost.override(TEST_TEMPLATE, `{{ hero.nam }}`); | 
					
						
							|  |  |  |     const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE).map(d => d.messageText); | 
					
						
							|  |  |  |     expect(diags).toContain( | 
					
						
							|  |  |  |         `Identifier 'nam' is not defined. 'Hero' does not contain such a member`); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2019-10-14 15:24:04 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 16:14:09 -07:00
										 |  |  |   it('should report access to a private member', () => { | 
					
						
							|  |  |  |     mockHost.override(TEST_TEMPLATE, `{{ myField }}`); | 
					
						
							|  |  |  |     const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE).map(d => d.messageText); | 
					
						
							|  |  |  |     expect(diags).toContain(`Identifier 'myField' refers to a private member of the component`); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2019-10-14 15:24:04 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 16:14:09 -07:00
										 |  |  |   it('should report numeric operator errors', () => { | 
					
						
							|  |  |  |     mockHost.override(TEST_TEMPLATE, `{{ 'a' % 2 }}`); | 
					
						
							|  |  |  |     const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE).map(d => d.messageText); | 
					
						
							|  |  |  |     expect(diags).toContain('Expected a number type'); | 
					
						
							| 
									
										
										
										
											2019-10-14 15:24:04 -07:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 20:04:56 -07:00
										 |  |  |   it('should report an unknown field', () => { | 
					
						
							|  |  |  |     mockHost.override(TEST_TEMPLATE, `<div *ngFor="let person of people"></div>`); | 
					
						
							|  |  |  |     const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE).map(d => d.messageText); | 
					
						
							|  |  |  |     expect(diags).toContain( | 
					
						
							|  |  |  |         `Identifier 'people' is not defined. ` + | 
					
						
							|  |  |  |         `The component declaration, template variable declarations, ` + | 
					
						
							|  |  |  |         `and element references do not contain such a member`); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2019-10-14 15:24:04 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 20:04:56 -07:00
										 |  |  |   it('should report an unknown value in a key expression', () => { | 
					
						
							|  |  |  |     mockHost.override(TEST_TEMPLATE, `<div *ngFor="let hero of heroes; trackBy: trackByFn"></div>`); | 
					
						
							|  |  |  |     const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE).map(d => d.messageText); | 
					
						
							|  |  |  |     expect(diags).toContain( | 
					
						
							|  |  |  |         `Identifier 'trackByFn' is not defined. ` + | 
					
						
							|  |  |  |         `The component declaration, template variable declarations, ` + | 
					
						
							|  |  |  |         `and element references do not contain such a member`); | 
					
						
							| 
									
										
										
										
											2019-10-14 15:24:04 -07:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-12 14:15:50 -08:00
										 |  |  |   describe('embedded templates', () => { | 
					
						
							|  |  |  |     it('should suggest refining a template context missing a property', () => { | 
					
						
							|  |  |  |       mockHost.override( | 
					
						
							|  |  |  |           TEST_TEMPLATE, | 
					
						
							| 
									
										
										
										
											2020-03-11 16:46:08 -07:00
										 |  |  |           `<button type="button" *counter="~{start-emb}let hero ~{end-emb}of heroes"></button>`); | 
					
						
							| 
									
										
										
										
											2020-01-12 14:15:50 -08:00
										 |  |  |       const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							|  |  |  |       expect(diags.length).toBe(1); | 
					
						
							| 
									
										
										
										
											2020-01-29 10:17:36 -08:00
										 |  |  |       const {messageText, start, length, category} = diags[0]; | 
					
						
							|  |  |  |       expect(category).toBe(ts.DiagnosticCategory.Suggestion); | 
					
						
							| 
									
										
										
										
											2020-01-12 14:15:50 -08:00
										 |  |  |       expect(messageText) | 
					
						
							|  |  |  |           .toBe( | 
					
						
							|  |  |  |               `The template context of 'CounterDirective' does not define an implicit value.\n` + | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |                   `If the context type is a base type or 'any', consider refining it to a more specific type.`, | 
					
						
							|  |  |  |           ); | 
					
						
							| 
									
										
										
										
											2020-01-12 14:15:50 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       const span = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'emb'); | 
					
						
							|  |  |  |       expect(start).toBe(span.start); | 
					
						
							|  |  |  |       expect(length).toBe(span.length); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should report an unknown context reference', () => { | 
					
						
							|  |  |  |       mockHost.override( | 
					
						
							|  |  |  |           TEST_TEMPLATE, | 
					
						
							| 
									
										
										
										
											2020-03-11 16:46:08 -07:00
										 |  |  |           `<div *ngFor="let hero of heroes; ~{start-emb}let e = even_1~{end-emb}"></div>`); | 
					
						
							| 
									
										
										
										
											2020-01-12 14:15:50 -08:00
										 |  |  |       const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							|  |  |  |       expect(diags.length).toBe(1); | 
					
						
							| 
									
										
										
										
											2020-01-29 10:17:36 -08:00
										 |  |  |       const {messageText, start, length, category} = diags[0]; | 
					
						
							|  |  |  |       expect(category).toBe(ts.DiagnosticCategory.Suggestion); | 
					
						
							| 
									
										
										
										
											2020-01-12 14:15:50 -08:00
										 |  |  |       expect(messageText) | 
					
						
							| 
									
										
										
										
											2020-01-29 10:17:36 -08:00
										 |  |  |           .toBe( | 
					
						
							|  |  |  |               `The template context of 'NgForOf' does not define a member called 'even_1'.\n` + | 
					
						
							|  |  |  |               `If the context type is a base type or 'any', consider refining it to a more specific type.`); | 
					
						
							| 
									
										
										
										
											2020-01-12 14:15:50 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       const span = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'emb'); | 
					
						
							|  |  |  |       expect(start).toBe(span.start); | 
					
						
							|  |  |  |       expect(length).toBe(span.length); | 
					
						
							| 
									
										
										
										
											2019-10-14 15:24:04 -07:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-02-19 20:38:21 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     it('report an unknown field in $implicit context', () => { | 
					
						
							|  |  |  |       mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							|  |  |  |         <div *withContext="let myVar"> | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |           {{ myVar.~{start-emb}missingField~{end-emb} }} | 
					
						
							| 
									
										
										
										
											2020-02-19 20:38:21 +01:00
										 |  |  |         </div> | 
					
						
							|  |  |  |       `);
 | 
					
						
							|  |  |  |       const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							|  |  |  |       expect(diags.length).toBe(1); | 
					
						
							|  |  |  |       const {messageText, start, length, category} = diags[0]; | 
					
						
							|  |  |  |       expect(category).toBe(ts.DiagnosticCategory.Error); | 
					
						
							|  |  |  |       expect(messageText) | 
					
						
							|  |  |  |           .toBe( | 
					
						
							| 
									
										
										
										
											2020-04-07 14:58:39 -07:00
										 |  |  |               `Identifier 'missingField' is not defined. '{ implicitPerson: Hero; }' does not contain such a member`, | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |           ); | 
					
						
							| 
									
										
										
										
											2020-02-19 20:38:21 +01:00
										 |  |  |       const span = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'emb'); | 
					
						
							|  |  |  |       expect(start).toBe(span.start); | 
					
						
							|  |  |  |       expect(length).toBe(span.length); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('report an unknown field in non implicit context', () => { | 
					
						
							|  |  |  |       mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							|  |  |  |         <div *withContext="let myVar = nonImplicitPerson"> | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |           {{ myVar.~{start-emb}missingField~{end-emb} }} | 
					
						
							| 
									
										
										
										
											2020-02-19 20:38:21 +01:00
										 |  |  |         </div> | 
					
						
							|  |  |  |       `);
 | 
					
						
							|  |  |  |       const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							|  |  |  |       expect(diags.length).toBe(1); | 
					
						
							|  |  |  |       const {messageText, start, length, category} = diags[0]; | 
					
						
							|  |  |  |       expect(category).toBe(ts.DiagnosticCategory.Error); | 
					
						
							|  |  |  |       expect(messageText) | 
					
						
							|  |  |  |           .toBe( | 
					
						
							| 
									
										
										
										
											2020-04-07 14:58:39 -07:00
										 |  |  |               `Identifier 'missingField' is not defined. 'Hero' does not contain such a member`, | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |           ); | 
					
						
							| 
									
										
										
										
											2020-02-19 20:38:21 +01:00
										 |  |  |       const span = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'emb'); | 
					
						
							|  |  |  |       expect(start).toBe(span.start); | 
					
						
							|  |  |  |       expect(length).toBe(span.length); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-10-14 15:24:04 -07:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   // #17611
 | 
					
						
							|  |  |  |   it('should not report diagnostic on iteration of any', () => { | 
					
						
							|  |  |  |     const fileName = '/app/test.ng'; | 
					
						
							|  |  |  |     mockHost.override(fileName, '<div *ngFor="let value of anyValue">{{value.someField}}</div>'); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const diagnostics = ngLS.getSemanticDiagnostics(fileName); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(diagnostics).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-06 10:32:45 -08:00
										 |  |  |   it('should report diagnostic for invalid property in nested ngFor', () => { | 
					
						
							|  |  |  |     const content = mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							|  |  |  |       <div *ngFor="let leagueMembers of league"> | 
					
						
							|  |  |  |         <div *ngFor="let member of leagueMembers"> | 
					
						
							|  |  |  |           {{member.xyz}} | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     `);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const diagnostics = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-11-06 10:32:45 -08:00
										 |  |  |     expect(diagnostics.length).toBe(1); | 
					
						
							|  |  |  |     const {messageText, start, length} = diagnostics[0]; | 
					
						
							|  |  |  |     expect(messageText) | 
					
						
							|  |  |  |         .toBe(`Identifier 'xyz' is not defined. 'Hero' does not contain such a member`); | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |     expect(content.substring(start!, start! + length!)).toBe('xyz'); | 
					
						
							| 
									
										
										
										
											2019-11-06 10:32:45 -08:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   describe('with $event', () => { | 
					
						
							|  |  |  |     it('should accept an event', () => { | 
					
						
							| 
									
										
										
										
											2019-12-26 13:26:54 -06:00
										 |  |  |       mockHost.override(TEST_TEMPLATE, '<div (click)="myClick($event)">Click me!</div>'); | 
					
						
							|  |  |  |       const diagnostics = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       expect(diagnostics).toEqual([]); | 
					
						
							| 
									
										
										
										
											2017-03-28 09:37:24 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     it('should reject it when not in an event binding', () => { | 
					
						
							| 
									
										
										
										
											2019-12-26 13:26:54 -06:00
										 |  |  |       const content = mockHost.override(TEST_TEMPLATE, '<div [tabIndex]="$event"></div>'); | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       const diagnostics = ngLS.getSemanticDiagnostics(TEST_TEMPLATE)!; | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       expect(diagnostics.length).toBe(1); | 
					
						
							|  |  |  |       const {messageText, start, length} = diagnostics[0]; | 
					
						
							|  |  |  |       expect(messageText) | 
					
						
							|  |  |  |           .toBe( | 
					
						
							| 
									
										
										
										
											2019-08-21 11:32:26 -07:00
										 |  |  |               `Identifier '$event' is not defined. The component declaration, template variable declarations, and element references do not contain such a member`); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       const keyword = '$event'; | 
					
						
							|  |  |  |       expect(start).toBe(content.lastIndexOf(keyword)); | 
					
						
							|  |  |  |       expect(length).toBe(keyword.length); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-12-26 13:26:54 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     it('should reject invalid properties on an event type', () => { | 
					
						
							|  |  |  |       const content = mockHost.override( | 
					
						
							|  |  |  |           TEST_TEMPLATE, '<div string-model (modelChange)="$event.notSubstring()"></div>'); | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       const diagnostics = ngLS.getSemanticDiagnostics(TEST_TEMPLATE)!; | 
					
						
							| 
									
										
										
										
											2019-12-26 13:26:54 -06:00
										 |  |  |       expect(diagnostics.length).toBe(1); | 
					
						
							|  |  |  |       const {messageText, start, length} = diagnostics[0]; | 
					
						
							| 
									
										
										
										
											2020-02-06 14:49:49 -08:00
										 |  |  |       expect(messageText) | 
					
						
							|  |  |  |           .toBe( | 
					
						
							|  |  |  |               `Identifier 'notSubstring' is not defined. 'string' does not contain such a member`); | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |       expect(content.substring(start!, start! + length!)).toBe('notSubstring'); | 
					
						
							| 
									
										
										
										
											2019-12-26 13:26:54 -06:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   it('should not crash with a incomplete *ngFor', () => { | 
					
						
							| 
									
										
										
										
											2019-09-10 11:21:12 -05:00
										 |  |  |     const fileName = mockHost.addCode(`
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       @Component({ | 
					
						
							|  |  |  |         template: '<div *ngFor></div> ~{after-div}' | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       export class MyComponent {}`);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     expect(() => ngLS.getSemanticDiagnostics(fileName)).not.toThrow(); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   it('should report a component not in a module', () => { | 
					
						
							| 
									
										
										
										
											2019-09-10 11:21:12 -05:00
										 |  |  |     const fileName = mockHost.addCode(`
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       @Component({ | 
					
						
							|  |  |  |         template: '<div></div>' | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       export class MyComponent {}`);
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |     const diagnostics = ngLS.getSemanticDiagnostics(fileName)!; | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(diagnostics.length).toBe(1); | 
					
						
							|  |  |  |     const {messageText, start, length} = diagnostics[0]; | 
					
						
							|  |  |  |     expect(messageText) | 
					
						
							|  |  |  |         .toBe( | 
					
						
							| 
									
										
										
										
											2019-08-21 11:32:26 -07:00
										 |  |  |             `Component 'MyComponent' is not included in a module and will not be available inside a template. Consider adding it to a NgModule declaration.`); | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |     const content = mockHost.readFile(fileName)!; | 
					
						
							|  |  |  |     expect(content.substring(start!, start! + length!)).toBe('MyComponent'); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   it(`should not report an error for a form's host directives`, () => { | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       import { Component } from '@angular/core'; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       @Component({ | 
					
						
							|  |  |  |         template: '<form></form>'}) | 
					
						
							|  |  |  |       export class AppComponent {}`);
 | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const tsDiags = tsLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(tsDiags).toEqual([]); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(ngDiags).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   it('should not throw getting diagnostics for an index expression', () => { | 
					
						
							| 
									
										
										
										
											2019-09-10 11:21:12 -05:00
										 |  |  |     const fileName = mockHost.addCode(`
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       @Component({ | 
					
						
							|  |  |  |         template: '<a *ngIf="(auth.isAdmin | async) || (event.leads && event.leads[(auth.uid | async)])"></a>' | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       export class MyComponent {}`);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     expect(() => ngLS.getSemanticDiagnostics(fileName)).not.toThrow(); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   it('should not throw using a directive with no value', () => { | 
					
						
							| 
									
										
										
										
											2019-09-10 11:21:12 -05:00
										 |  |  |     const fileName = mockHost.addCode(`
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       @Component({ | 
					
						
							|  |  |  |         template: '<form><input [(ngModel)]="name" required /></form>' | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       export class MyComponent { | 
					
						
							|  |  |  |         name = 'some name'; | 
					
						
							|  |  |  |       }`);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     expect(() => ngLS.getSemanticDiagnostics(fileName)).not.toThrow(); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   it('should report an error for invalid metadata', () => { | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const content = mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       import { Component } from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @Component({ | 
					
						
							|  |  |  |         template: '<div></div>', | 
					
						
							|  |  |  |         providers: [ | 
					
						
							|  |  |  |           {provide: 'foo', useFactory: () => 'foo' } | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       export class AppComponent { | 
					
						
							|  |  |  |         name = 'some name'; | 
					
						
							|  |  |  |       }`);
 | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const tsDiags = tsLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(tsDiags).toEqual([]); | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(APP_COMPONENT)!; | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(ngDiags.length).toBe(1); | 
					
						
							|  |  |  |     const {messageText, start, length} = ngDiags[0]; | 
					
						
							|  |  |  |     const keyword = `() => 'foo'`; | 
					
						
							|  |  |  |     expect(start).toBe(content.lastIndexOf(keyword)); | 
					
						
							|  |  |  |     expect(length).toBe(keyword.length); | 
					
						
							|  |  |  |     // messageText is a three-part chain
 | 
					
						
							|  |  |  |     const firstPart = messageText as ts.DiagnosticMessageChain; | 
					
						
							| 
									
										
										
										
											2019-08-21 11:32:26 -07:00
										 |  |  |     expect(firstPart.messageText).toBe(`Error during template compile of 'AppComponent'`); | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |     const secondPart = firstPart.next!; | 
					
						
							| 
									
										
										
										
											2019-10-01 16:44:50 -07:00
										 |  |  |     expect(secondPart[0].messageText).toBe('Function expressions are not supported in decorators'); | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |     const thirdPart = secondPart[0].next!; | 
					
						
							| 
									
										
										
										
											2019-10-01 16:44:50 -07:00
										 |  |  |     expect(thirdPart[0].messageText) | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |         .toBe('Consider changing the function expression into an exported function'); | 
					
						
							| 
									
										
										
										
											2019-10-01 16:44:50 -07:00
										 |  |  |     expect(thirdPart[0].next).toBeFalsy(); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-12-06 09:56:30 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   it('should not throw for an invalid class', () => { | 
					
						
							| 
									
										
										
										
											2019-09-10 11:21:12 -05:00
										 |  |  |     const fileName = mockHost.addCode(`
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       @Component({ | 
					
						
							|  |  |  |         template: '' | 
					
						
							|  |  |  |       }) class`);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     expect(() => ngLS.getSemanticDiagnostics(fileName)).not.toThrow(); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2018-12-14 13:40:01 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-19 17:04:02 -07:00
										 |  |  |   it('should not report an error for sub-types of string in non-strict mode', () => { | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       import { Component } from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @Component({ | 
					
						
							|  |  |  |         template: \`<div *ngIf="something === 'foo'"></div>\`
 | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       export class AppComponent { | 
					
						
							|  |  |  |         something: 'foo' | 'bar'; | 
					
						
							|  |  |  |       }`);
 | 
					
						
							| 
									
										
										
										
											2019-09-19 17:04:02 -07:00
										 |  |  |     mockHost.overrideOptions({ | 
					
						
							|  |  |  |       strict: false,  // TODO: this test fails in strict mode
 | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const tsDiags = tsLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(tsDiags).toEqual([]); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(ngDiags).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-12-13 11:20:45 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-19 17:04:02 -07:00
										 |  |  |   it('should not report an error for sub-types of number in non-strict mode', () => { | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       import { Component } from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @Component({ | 
					
						
							|  |  |  |         template: '<div *ngIf="something === 123"></div>' | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       export class AppComponent { | 
					
						
							|  |  |  |         something: 123 | 456; | 
					
						
							|  |  |  |       }`);
 | 
					
						
							| 
									
										
										
										
											2019-09-19 17:04:02 -07:00
										 |  |  |     mockHost.overrideOptions({ | 
					
						
							|  |  |  |       strict: false,  // TODO: This test fails in strict mode
 | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const tsDiags = tsLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(tsDiags).toEqual([]); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(ngDiags).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-12-12 18:11:56 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   it('should report a warning if an event results in a callable expression', () => { | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const content = mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       import { Component } from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @Component({ | 
					
						
							|  |  |  |         template: '<div (click)="onClick"></div>' | 
					
						
							|  |  |  |       }) | 
					
						
							| 
									
										
										
										
											2019-09-19 17:04:02 -07:00
										 |  |  |       export class AppComponent { | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |         onClick() { } | 
					
						
							|  |  |  |       }`);
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |     const diagnostics = ngLS.getSemanticDiagnostics(APP_COMPONENT)!; | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     const {messageText, start, length} = diagnostics[0]; | 
					
						
							|  |  |  |     expect(messageText).toBe('Unexpected callable expression. Expected a method call'); | 
					
						
							|  |  |  |     const keyword = `"onClick"`; | 
					
						
							|  |  |  |     expect(start).toBe(content.lastIndexOf(keyword) + 1);  // exclude leading quote
 | 
					
						
							|  |  |  |     expect(length).toBe(keyword.length - 2);               // exclude leading and trailing quotes
 | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-12-12 15:59:12 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   // Issue #13326
 | 
					
						
							|  |  |  |   it('should report a narrow span for invalid pipes', () => { | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const content = mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       import { Component } from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @Component({ | 
					
						
							|  |  |  |         template: '<p> Using an invalid pipe {{data | dat}} </p>' | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       export class AppComponent { | 
					
						
							|  |  |  |         data = 'some data'; | 
					
						
							|  |  |  |       }`);
 | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const tsDiags = tsLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(tsDiags).toEqual([]); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(ngDiags.length).toBe(1); | 
					
						
							|  |  |  |     const {messageText, start, length} = ngDiags[0]; | 
					
						
							|  |  |  |     expect(messageText).toBe(`The pipe 'dat' could not be found`); | 
					
						
							|  |  |  |     const keyword = 'data | dat'; | 
					
						
							|  |  |  |     expect(start).toBe(content.lastIndexOf(keyword)); | 
					
						
							|  |  |  |     expect(length).toBe(keyword.length); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-11-22 12:09:49 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   // Issue #19406
 | 
					
						
							|  |  |  |   it('should allow empty template', () => { | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       import { Component } from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @Component({ | 
					
						
							|  |  |  |         template : '', | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       export class AppComponent {}`);
 | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const tsDiags = tsLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(tsDiags).toEqual([]); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(ngDiags).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-04-04 08:30:38 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   // Issue #15460
 | 
					
						
							|  |  |  |   it('should be able to find members defined on an ancestor type', () => { | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       import { Component } from '@angular/core'; | 
					
						
							|  |  |  |       import { NgForm } from '@angular/forms'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @Component({ | 
					
						
							|  |  |  |         selector: 'example-app', | 
					
						
							|  |  |  |         template: \`
 | 
					
						
							|  |  |  |            <form #f="ngForm" (ngSubmit)="onSubmit(f)" novalidate> | 
					
						
							|  |  |  |             <input name="first" ngModel required #first="ngModel"> | 
					
						
							|  |  |  |             <input name="last" ngModel> | 
					
						
							|  |  |  |             <button>Submit</button> | 
					
						
							|  |  |  |           </form> | 
					
						
							|  |  |  |           <p>First name value: {{ first.value }}</p> | 
					
						
							|  |  |  |           <p>First name valid: {{ first.valid }}</p> | 
					
						
							|  |  |  |           <p>Form value: {{ f.value | json }}</p> | 
					
						
							|  |  |  |           <p>Form valid: {{ f.valid }}</p> | 
					
						
							|  |  |  |        \`,
 | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       export class AppComponent { | 
					
						
							|  |  |  |         onSubmit(form: NgForm) {} | 
					
						
							|  |  |  |       }`);
 | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const tsDiags = tsLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(tsDiags).toEqual([]); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(ngDiags).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-04-04 11:37:06 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   it('should report an error for invalid providers', () => { | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const content = mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       import { Component } from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @Component({ | 
					
						
							|  |  |  |         template: '', | 
					
						
							|  |  |  |         providers: [null] | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       export class AppComponent {}`);
 | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const tsDiags = tsLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-09-19 17:04:02 -07:00
										 |  |  |     expect(tsDiags.length).toBe(1); | 
					
						
							|  |  |  |     const msgText = ts.flattenDiagnosticMessageText(tsDiags[0].messageText, '\n'); | 
					
						
							| 
									
										
										
										
											2020-03-04 17:27:27 -08:00
										 |  |  |     expect(msgText).toBe(`Type 'null' is not assignable to type 'Provider'.`); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(ngDiags.length).toBe(1); | 
					
						
							|  |  |  |     const {messageText, start, length} = ngDiags[0]; | 
					
						
							|  |  |  |     expect(messageText) | 
					
						
							|  |  |  |         .toBe( | 
					
						
							|  |  |  |             'Invalid providers for "AppComponent in /app/app.component.ts" - only instances of Provider and Type are allowed, got: [?null?]'); | 
					
						
							|  |  |  |     // TODO: Looks like this is the wrong span. Should point to 'null' instead.
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |     expect(content.substring(start!, start! + length!)).toBe('AppComponent'); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-12-12 16:42:20 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   // Issue #15768
 | 
					
						
							|  |  |  |   it('should be able to parse a template reference', () => { | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       import { Component } from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @Component({ | 
					
						
							|  |  |  |         selector: 'my-component', | 
					
						
							|  |  |  |         template: \`
 | 
					
						
							|  |  |  |           <div *ngIf="comps | async; let comps; else loading"> | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |           <ng-template #loading>Loading comps...</ng-template> | 
					
						
							|  |  |  |         \`
 | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       export class AppComponent {}`);
 | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const tsDiags = tsLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(tsDiags).toEqual([]); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(ngDiags).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-04-04 10:31:01 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   // Issue #15625
 | 
					
						
							|  |  |  |   it('should not report errors for localization syntax', () => { | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       import { Component } from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @Component({ | 
					
						
							|  |  |  |         selector: 'my-component', | 
					
						
							|  |  |  |         template: \`
 | 
					
						
							|  |  |  |         <div> | 
					
						
							|  |  |  |             {fieldCount, plural, =0 {no fields} =1 {1 field} other {{{fieldCount}} fields}} | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |         \`
 | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       export class AppComponent { | 
					
						
							| 
									
										
										
										
											2019-09-19 17:04:02 -07:00
										 |  |  |         fieldCount: number = 0; | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       }`);
 | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const tsDiags = tsLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(tsDiags).toEqual([]); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(ngDiags).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-04-13 16:19:59 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   // Issue #15885
 | 
					
						
							|  |  |  |   it('should be able to remove null and undefined from a type', () => { | 
					
						
							| 
									
										
										
										
											2019-09-19 17:04:02 -07:00
										 |  |  |     mockHost.overrideOptions({ | 
					
						
							|  |  |  |       strictNullChecks: true, | 
					
						
							| 
									
										
										
										
											2017-04-10 15:10:34 -07:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       import { Component } from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @Component({ | 
					
						
							|  |  |  |         selector: 'my-component', | 
					
						
							|  |  |  |         template: '{{test?.a}}', | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       export class AppComponent { | 
					
						
							|  |  |  |         test: {a: number, b: number} | null = { | 
					
						
							|  |  |  |           a: 1, | 
					
						
							|  |  |  |           b: 2, | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       }`);
 | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const tsDiags = tsLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(tsDiags).toEqual([]); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(ngDiags).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-04-10 15:10:34 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-20 11:31:15 -08:00
										 |  |  |   // Issue https://github.com/angular/angular/issues/34874
 | 
					
						
							|  |  |  |   it('should recognize inputs and outputs listed inside directive decorators', () => { | 
					
						
							|  |  |  |     mockHost.override( | 
					
						
							|  |  |  |         TEST_TEMPLATE, `<div hint-model [hint]="title" (hintChange)="myClick($event)"></div>`); | 
					
						
							|  |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							|  |  |  |     expect(ngDiags).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   it('should be able to resolve modules using baseUrl', () => { | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       import { Component } from '@angular/core'; | 
					
						
							|  |  |  |       import { NgForm } from '@angular/forms'; | 
					
						
							|  |  |  |       import { Server } from 'app/server'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @Component({ | 
					
						
							|  |  |  |         selector: 'example-app', | 
					
						
							|  |  |  |         template: '...', | 
					
						
							|  |  |  |         providers: [Server] | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       export class AppComponent { | 
					
						
							|  |  |  |         onSubmit(form: NgForm) {} | 
					
						
							|  |  |  |       }`);
 | 
					
						
							|  |  |  |     mockHost.addScript('/other/files/app/server.ts', 'export class Server {}'); | 
					
						
							| 
									
										
										
										
											2019-09-19 17:04:02 -07:00
										 |  |  |     mockHost.overrideOptions({ | 
					
						
							|  |  |  |       baseUrl: '/other/files', | 
					
						
							| 
									
										
										
										
											2017-09-28 09:30:34 -07:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const tsDiags = tsLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(tsDiags).toEqual([]); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const diagnostic = ngLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(diagnostic).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-09-28 09:30:34 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |   it('should report errors for using the now removed OpaqueToken (deprecated)', () => { | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |       import { Component, Inject, OpaqueToken } from '@angular/core'; | 
					
						
							|  |  |  |       import { NgForm } from '@angular/forms'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       export const token = new OpaqueToken('some token'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @Component({ | 
					
						
							|  |  |  |         selector: 'example-app', | 
					
						
							|  |  |  |         template: '...' | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       export class AppComponent { | 
					
						
							|  |  |  |         constructor (@Inject(token) value: string) {} | 
					
						
							|  |  |  |         onSubmit(form: NgForm) {} | 
					
						
							|  |  |  |       }`);
 | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |     const tsDiags = tsLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     expect(tsDiags.length).toBe(1); | 
					
						
							|  |  |  |     expect(tsDiags[0].messageText) | 
					
						
							|  |  |  |         .toBe( | 
					
						
							|  |  |  |             `Module '"../node_modules/@angular/core/core"' has no exported member 'OpaqueToken'.`); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-10 14:51:16 -05:00
										 |  |  |   describe('templates', () => { | 
					
						
							| 
									
										
										
										
											2019-09-10 10:55:12 -05:00
										 |  |  |     it('should report errors for invalid templateUrls', () => { | 
					
						
							|  |  |  |       const fileName = mockHost.addCode(`
 | 
					
						
							| 
									
										
										
										
											2019-10-14 15:24:04 -07:00
										 |  |  |         @Component({ | 
					
						
							| 
									
										
										
										
											2019-09-10 10:55:12 -05:00
										 |  |  |           templateUrl: '«notAFile»', | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         export class MyComponent {}`);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const marker = mockHost.getReferenceMarkerFor(fileName, 'notAFile'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       const diagnostics = ngLS.getSemanticDiagnostics(fileName)!; | 
					
						
							| 
									
										
										
										
											2019-09-10 10:55:12 -05:00
										 |  |  |       const urlDiagnostic = | 
					
						
							|  |  |  |           diagnostics.find(d => d.messageText === 'URL does not point to a valid file'); | 
					
						
							|  |  |  |       expect(urlDiagnostic).toBeDefined(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       const {start, length} = urlDiagnostic!; | 
					
						
							| 
									
										
										
										
											2019-09-10 10:55:12 -05:00
										 |  |  |       expect(start).toBe(marker.start); | 
					
						
							|  |  |  |       expect(length).toBe(marker.length); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should not report errors for valid templateUrls', () => { | 
					
						
							|  |  |  |       const fileName = mockHost.addCode(`
 | 
					
						
							| 
									
										
										
										
											2019-10-14 15:24:04 -07:00
										 |  |  |         @Component({ | 
					
						
							| 
									
										
										
										
											2019-09-10 10:55:12 -05:00
										 |  |  |           templateUrl: './test.ng', | 
					
						
							| 
									
										
										
										
											2019-10-14 15:24:04 -07:00
										 |  |  |         }) | 
					
						
							|  |  |  |         export class MyComponent {}`);
 | 
					
						
							| 
									
										
										
										
											2019-09-10 10:55:12 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       const diagnostics = ngLS.getSemanticDiagnostics(fileName)!; | 
					
						
							| 
									
										
										
										
											2019-09-10 10:55:12 -05:00
										 |  |  |       const urlDiagnostic = | 
					
						
							|  |  |  |           diagnostics.find(d => d.messageText === 'URL does not point to a valid file'); | 
					
						
							|  |  |  |       expect(urlDiagnostic).toBeUndefined(); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-09-13 13:58:01 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-10 14:51:16 -05:00
										 |  |  |     it('should report diagnostic for missing template or templateUrl', () => { | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |       const content = mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-09-18 14:11:21 -07:00
										 |  |  |         import {Component} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-10 14:51:16 -05:00
										 |  |  |         @Component({ | 
					
						
							|  |  |  |           selector: 'app-example', | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2019-09-18 14:11:21 -07:00
										 |  |  |         export class AppComponent {}`);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |       const diags = ngLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-09-18 14:11:21 -07:00
										 |  |  |       expect(diags.length).toBe(1); | 
					
						
							|  |  |  |       const {file, messageText, start, length} = diags[0]; | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       expect(file!.fileName).toBe(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-09-18 14:11:21 -07:00
										 |  |  |       expect(messageText).toBe(`Component 'AppComponent' must have a template or templateUrl`); | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       expect(content.substring(start!, start! + length!)).toBe('AppComponent'); | 
					
						
							| 
									
										
										
										
											2019-09-10 14:51:16 -05:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should report diagnostic for both template and templateUrl', () => { | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |       const content = mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-09-18 14:11:21 -07:00
										 |  |  |         import {Component} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-10 14:51:16 -05:00
										 |  |  |         @Component({ | 
					
						
							|  |  |  |           selector: 'app-example', | 
					
						
							|  |  |  |           template: '<div></div>', | 
					
						
							| 
									
										
										
										
											2019-09-18 14:11:21 -07:00
										 |  |  |           templateUrl: './test.ng', | 
					
						
							| 
									
										
										
										
											2019-09-10 14:51:16 -05:00
										 |  |  |         }) | 
					
						
							| 
									
										
										
										
											2019-09-18 14:11:21 -07:00
										 |  |  |         export class AppComponent {}`);
 | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |       const diags = ngLS.getSemanticDiagnostics(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-09-18 14:11:21 -07:00
										 |  |  |       expect(diags.length).toBe(1); | 
					
						
							|  |  |  |       const {file, messageText, start, length} = diags[0]; | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       expect(file!.fileName).toBe(APP_COMPONENT); | 
					
						
							| 
									
										
										
										
											2019-09-18 14:11:21 -07:00
										 |  |  |       expect(messageText) | 
					
						
							|  |  |  |           .toBe(`Component 'AppComponent' must not have both template and templateUrl`); | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       expect(content.substring(start!, start! + length!)).toBe('AppComponent'); | 
					
						
							| 
									
										
										
										
											2019-09-10 14:51:16 -05:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-13 13:58:01 -05:00
										 |  |  |     it('should report errors for invalid styleUrls', () => { | 
					
						
							|  |  |  |       const fileName = mockHost.addCode(`
 | 
					
						
							|  |  |  |         @Component({ | 
					
						
							|  |  |  |           styleUrls: ['«notAFile»'], | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         export class MyComponent {}`);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const marker = mockHost.getReferenceMarkerFor(fileName, 'notAFile'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       const diagnostics = ngLS.getSemanticDiagnostics(fileName)!; | 
					
						
							| 
									
										
										
										
											2019-09-13 13:58:01 -05:00
										 |  |  |       const urlDiagnostic = | 
					
						
							|  |  |  |           diagnostics.find(d => d.messageText === 'URL does not point to a valid file'); | 
					
						
							|  |  |  |       expect(urlDiagnostic).toBeDefined(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       const {start, length} = urlDiagnostic!; | 
					
						
							| 
									
										
										
										
											2019-09-13 13:58:01 -05:00
										 |  |  |       expect(start).toBe(marker.start); | 
					
						
							|  |  |  |       expect(length).toBe(marker.length); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should not report errors for valid styleUrls', () => { | 
					
						
							| 
									
										
										
										
											2019-12-28 13:57:55 -06:00
										 |  |  |       mockHost.override(APP_COMPONENT, `
 | 
					
						
							| 
									
										
										
										
											2019-09-19 17:04:02 -07:00
										 |  |  |         import {Component} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-13 13:58:01 -05:00
										 |  |  |         @Component({ | 
					
						
							| 
									
										
										
										
											2019-09-19 17:04:02 -07:00
										 |  |  |           template: '<div></div>', | 
					
						
							| 
									
										
										
										
											2019-09-13 13:58:01 -05:00
										 |  |  |           styleUrls: ['./test.css', './test.css'], | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2019-09-19 17:04:02 -07:00
										 |  |  |         export class AppComponent {}`);
 | 
					
						
							| 
									
										
										
										
											2019-09-13 13:58:01 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       const diagnostics = ngLS.getSemanticDiagnostics(APP_COMPONENT)!; | 
					
						
							| 
									
										
										
										
											2019-09-13 13:58:01 -05:00
										 |  |  |       expect(diagnostics.length).toBe(0); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-09-10 10:55:12 -05:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-10 17:22:59 -08:00
										 |  |  |   it('should work correctly with CRLF endings in external template', () => { | 
					
						
							| 
									
										
										
										
											2019-10-17 15:26:10 -07:00
										 |  |  |     // https://github.com/angular/vscode-ng-language-service/issues/235
 | 
					
						
							|  |  |  |     // In the example below, the string
 | 
					
						
							|  |  |  |     // `\r\n{{line0}}\r\n{{line1}}\r\n{{line2}}` is tokenized as a whole,
 | 
					
						
							|  |  |  |     // and then CRLF characters are converted to LF.
 | 
					
						
							|  |  |  |     // Source span information is lost in the process.
 | 
					
						
							| 
									
										
										
										
											2020-01-10 17:22:59 -08:00
										 |  |  |     const content = mockHost.override( | 
					
						
							|  |  |  |         TEST_TEMPLATE, '\r\n<div>\r\n{{line0}}\r\n{{line1}}\r\n{{line2}}\r\n</div>'); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							| 
									
										
										
										
											2019-08-21 11:32:26 -07:00
										 |  |  |     expect(ngDiags.length).toBe(3); | 
					
						
							|  |  |  |     for (let i = 0; i < 3; ++i) { | 
					
						
							|  |  |  |       const {messageText, start, length} = ngDiags[i]; | 
					
						
							|  |  |  |       expect(messageText) | 
					
						
							|  |  |  |           .toBe( | 
					
						
							| 
									
										
										
										
											2019-10-17 15:26:10 -07:00
										 |  |  |               `Identifier 'line${i}' is not defined. ` + | 
					
						
							|  |  |  |               `The component declaration, template variable declarations, and ` + | 
					
						
							|  |  |  |               `element references do not contain such a member`); | 
					
						
							| 
									
										
										
										
											2019-08-21 11:32:26 -07:00
										 |  |  |       // Assert that the span is actually highlight the bounded text. The span
 | 
					
						
							|  |  |  |       // would be off if CRLF endings are not handled properly.
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       expect(content.substring(start!, start! + length!)).toBe(`line${i}`); | 
					
						
							| 
									
										
										
										
											2019-08-21 11:32:26 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2019-11-27 15:46:58 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-10 17:22:59 -08:00
										 |  |  |   it('should work correctly with CRLF endings in inline template', () => { | 
					
						
							|  |  |  |     const fileName = mockHost.addCode( | 
					
						
							|  |  |  |         '\n@Component({template:`\r\n\r\n{{line}}`})export class ComponentCRLF {}'); | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |     const content = mockHost.readFile(fileName)!; | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(fileName); | 
					
						
							| 
									
										
										
										
											2020-01-10 17:22:59 -08:00
										 |  |  |     expect(ngDiags.length).toBeGreaterThan(0); | 
					
						
							|  |  |  |     const {messageText, start, length} = ngDiags[0]; | 
					
						
							|  |  |  |     expect(messageText) | 
					
						
							|  |  |  |         .toBe( | 
					
						
							|  |  |  |             `Identifier 'line' is not defined. ` + | 
					
						
							|  |  |  |             `The component declaration, template variable declarations, and ` + | 
					
						
							|  |  |  |             `element references do not contain such a member`); | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |     expect(content.substring(start!, start! + length!)).toBe('line'); | 
					
						
							| 
									
										
										
										
											2020-01-10 17:22:59 -08:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-27 15:46:58 -08:00
										 |  |  |   it('should not produce diagnostics for non-exported directives', () => { | 
					
						
							|  |  |  |     const fileName = '/app/test.component.ts'; | 
					
						
							|  |  |  |     mockHost.addScript(fileName, `
 | 
					
						
							|  |  |  |       import {Component} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @Component({ | 
					
						
							|  |  |  |         template: '<test-comp></test-comp>' | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       class TestHostComponent {} | 
					
						
							|  |  |  |     `);
 | 
					
						
							|  |  |  |     const tsDiags = tsLS.getSemanticDiagnostics(fileName); | 
					
						
							|  |  |  |     expect(tsDiags).toEqual([]); | 
					
						
							| 
									
										
										
										
											2020-01-21 14:51:43 -08:00
										 |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(fileName); | 
					
						
							| 
									
										
										
										
											2019-11-27 15:46:58 -08:00
										 |  |  |     expect(ngDiags).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2020-02-06 14:49:49 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   it('should suggest ? or ! operator if method receiver is nullable', () => { | 
					
						
							|  |  |  |     const content = mockHost.override(TEST_TEMPLATE, `{{optional && optional.toLowerCase()}}`); | 
					
						
							|  |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							|  |  |  |     expect(ngDiags.length).toBe(1); | 
					
						
							|  |  |  |     const {start, length, messageText, category} = ngDiags[0]; | 
					
						
							|  |  |  |     expect(messageText) | 
					
						
							|  |  |  |         .toBe( | 
					
						
							|  |  |  |             `'optional' is possibly undefined. ` + | 
					
						
							|  |  |  |             `Consider using the safe navigation operator (optional?.toLowerCase) ` + | 
					
						
							|  |  |  |             `or non-null assertion operator (optional!.toLowerCase).`); | 
					
						
							|  |  |  |     expect(category).toBe(ts.DiagnosticCategory.Suggestion); | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |     expect(content.substring(start!, start! + length!)).toBe('toLowerCase'); | 
					
						
							| 
									
										
										
										
											2020-02-06 14:49:49 -08:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should suggest ? or ! operator if property receiver is nullable', () => { | 
					
						
							|  |  |  |     const content = mockHost.override(TEST_TEMPLATE, `{{optional && optional.length}}`); | 
					
						
							|  |  |  |     const ngDiags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							|  |  |  |     expect(ngDiags.length).toBe(1); | 
					
						
							|  |  |  |     const {start, length, messageText, category} = ngDiags[0]; | 
					
						
							|  |  |  |     expect(messageText) | 
					
						
							|  |  |  |         .toBe( | 
					
						
							|  |  |  |             `'optional' is possibly undefined. ` + | 
					
						
							|  |  |  |             `Consider using the safe navigation operator (optional?.length) ` + | 
					
						
							|  |  |  |             `or non-null assertion operator (optional!.length).`); | 
					
						
							|  |  |  |     expect(category).toBe(ts.DiagnosticCategory.Suggestion); | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |     expect(content.substring(start!, start! + length!)).toBe('length'); | 
					
						
							| 
									
										
										
										
											2020-02-06 14:49:49 -08:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |   it('should report error if method is not found on non-nullable receivers', () => { | 
					
						
							| 
									
										
										
										
											2020-02-06 14:49:49 -08:00
										 |  |  |     const expressions = [ | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |       'optional?', | 
					
						
							|  |  |  |       'optional!', | 
					
						
							| 
									
										
										
										
											2020-02-06 14:49:49 -08:00
										 |  |  |     ]; | 
					
						
							|  |  |  |     for (const expression of expressions) { | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |       const content = mockHost.override(TEST_TEMPLATE, `{{ ${expression}.someMethod() }}`); | 
					
						
							| 
									
										
										
										
											2020-02-06 14:49:49 -08:00
										 |  |  |       const ngDiags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							|  |  |  |       expect(ngDiags.length).toBe(1); | 
					
						
							|  |  |  |       const {start, length, messageText, category} = ngDiags[0]; | 
					
						
							|  |  |  |       expect(messageText) | 
					
						
							|  |  |  |           .toBe(`Identifier 'someMethod' is not defined. 'string' does not contain such a member`); | 
					
						
							|  |  |  |       expect(category).toBe(ts.DiagnosticCategory.Error); | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |       expect(content.substring(start!, start! + length!)).toBe('someMethod'); | 
					
						
							| 
									
										
										
										
											2020-02-06 14:49:49 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |   it('should report error if property is not found on non-nullable receivers', () => { | 
					
						
							| 
									
										
										
										
											2020-02-06 14:49:49 -08:00
										 |  |  |     const expressions = [ | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |       'optional?', | 
					
						
							|  |  |  |       'optional!', | 
					
						
							| 
									
										
										
										
											2020-02-06 14:49:49 -08:00
										 |  |  |     ]; | 
					
						
							|  |  |  |     for (const expression of expressions) { | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |       const content = mockHost.override(TEST_TEMPLATE, `{{ ${expression}.someProp }}`); | 
					
						
							| 
									
										
										
										
											2020-02-06 14:49:49 -08:00
										 |  |  |       const ngDiags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							|  |  |  |       expect(ngDiags.length).toBe(1); | 
					
						
							|  |  |  |       const {start, length, messageText, category} = ngDiags[0]; | 
					
						
							|  |  |  |       expect(messageText) | 
					
						
							|  |  |  |           .toBe(`Identifier 'someProp' is not defined. 'string' does not contain such a member`); | 
					
						
							|  |  |  |       expect(category).toBe(ts.DiagnosticCategory.Error); | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |       expect(content.substring(start!, start! + length!)).toBe('someProp'); | 
					
						
							| 
									
										
										
										
											2020-02-06 14:49:49 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   describe('function calls', () => { | 
					
						
							|  |  |  |     it('should report error for non-callable function call', () => { | 
					
						
							|  |  |  |       mockHost.override(TEST_TEMPLATE, `
 | 
					
						
							|  |  |  |         <p>{{myClick()()}}</p> | 
					
						
							|  |  |  |       `);
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       const content = mockHost.readFile(TEST_TEMPLATE)!; | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |       const diags = ngLS.getSemanticDiagnostics(TEST_TEMPLATE); | 
					
						
							|  |  |  |       expect(diags.length).toBe(1); | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       const {messageText, start, length} = diags[0]!; | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |       expect(messageText).toBe(`Call target 'myClick()' has non-callable type 'void'.`); | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       expect(content.substring(start!, start! + length!)).toBe('myClick()()'); | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | }); |