fix(language-service): Turn on strict mode for test project (#32783)

This is the last part in refactoring of the test project.
This PR turns on strict mode for typechecking and fixed tests that
fail under this mode.

PR Close #32783
This commit is contained in:
Keen Yee Liau 2019-09-19 17:04:02 -07:00 committed by atscott
parent 9e7aa60ae7
commit 28358b6395
8 changed files with 70 additions and 44 deletions

View File

@ -152,10 +152,9 @@ describe('completions', () => {
}); });
it('should respect paths configuration', () => { it('should respect paths configuration', () => {
mockHost.overrideOptions(options => { mockHost.overrideOptions({
options.baseUrl = '/app'; baseUrl: '/app',
options.paths = {'bar/*': ['foo/bar/*']}; paths: {'bar/*': ['foo/bar/*']},
return options;
}); });
mockHost.addScript('/app/foo/bar/shared.ts', ` mockHost.addScript('/app/foo/bar/shared.ts', `
export interface Node { export interface Node {

View File

@ -191,7 +191,7 @@ describe('diagnostics', () => {
expect(() => ngLS.getDiagnostics(fileName)).not.toThrow(); expect(() => ngLS.getDiagnostics(fileName)).not.toThrow();
}); });
it('should not report an error for sub-types of string', () => { it('should not report an error for sub-types of string in non-strict mode', () => {
const fileName = '/app/app.component.ts'; const fileName = '/app/app.component.ts';
mockHost.override(fileName, ` mockHost.override(fileName, `
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@ -202,13 +202,16 @@ describe('diagnostics', () => {
export class AppComponent { export class AppComponent {
something: 'foo' | 'bar'; something: 'foo' | 'bar';
}`); }`);
mockHost.overrideOptions({
strict: false, // TODO: this test fails in strict mode
});
const tsDiags = tsLS.getSemanticDiagnostics(fileName); const tsDiags = tsLS.getSemanticDiagnostics(fileName);
expect(tsDiags).toEqual([]); expect(tsDiags).toEqual([]);
const ngDiags = ngLS.getDiagnostics(fileName); const ngDiags = ngLS.getDiagnostics(fileName);
expect(ngDiags).toEqual([]); expect(ngDiags).toEqual([]);
}); });
it('should not report an error for sub-types of number', () => { it('should not report an error for sub-types of number in non-strict mode', () => {
const fileName = '/app/app.component.ts'; const fileName = '/app/app.component.ts';
mockHost.override(fileName, ` mockHost.override(fileName, `
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@ -219,6 +222,9 @@ describe('diagnostics', () => {
export class AppComponent { export class AppComponent {
something: 123 | 456; something: 123 | 456;
}`); }`);
mockHost.overrideOptions({
strict: false, // TODO: This test fails in strict mode
});
const tsDiags = tsLS.getSemanticDiagnostics(fileName); const tsDiags = tsLS.getSemanticDiagnostics(fileName);
expect(tsDiags).toEqual([]); expect(tsDiags).toEqual([]);
const ngDiags = ngLS.getDiagnostics(fileName); const ngDiags = ngLS.getDiagnostics(fileName);
@ -233,7 +239,7 @@ describe('diagnostics', () => {
@Component({ @Component({
template: '<div (click)="onClick"></div>' template: '<div (click)="onClick"></div>'
}) })
export class MyComponent { export class AppComponent {
onClick() { } onClick() { }
}`); }`);
const diagnostics = ngLS.getDiagnostics(fileName) !; const diagnostics = ngLS.getDiagnostics(fileName) !;
@ -245,7 +251,7 @@ describe('diagnostics', () => {
}); });
// #13412 // #13412
it('should not report an error for using undefined', () => { it('should not report an error for using undefined under non-strict mode', () => {
const fileName = '/app/app.component.ts'; const fileName = '/app/app.component.ts';
mockHost.override(fileName, ` mockHost.override(fileName, `
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@ -256,6 +262,9 @@ describe('diagnostics', () => {
export class AppComponent { export class AppComponent {
something = 'foo'; something = 'foo';
}`); }`);
mockHost.overrideOptions({
strict: false, // TODO: This test fails in strict mode
});
const tsDiags = tsLS.getSemanticDiagnostics(fileName); const tsDiags = tsLS.getSemanticDiagnostics(fileName);
expect(tsDiags).toEqual([]); expect(tsDiags).toEqual([]);
const ngDiags = ngLS.getDiagnostics(fileName); const ngDiags = ngLS.getDiagnostics(fileName);
@ -342,7 +351,10 @@ describe('diagnostics', () => {
}) })
export class AppComponent {}`); export class AppComponent {}`);
const tsDiags = tsLS.getSemanticDiagnostics(fileName); const tsDiags = tsLS.getSemanticDiagnostics(fileName);
expect(tsDiags).toEqual([]); expect(tsDiags.length).toBe(1);
const msgText = ts.flattenDiagnosticMessageText(tsDiags[0].messageText, '\n');
expect(msgText).toBe(
`Type 'null[]' is not assignable to type 'Provider[]'.\n Type 'null' is not assignable to type 'Provider'.`);
const ngDiags = ngLS.getDiagnostics(fileName); const ngDiags = ngLS.getDiagnostics(fileName);
expect(ngDiags.length).toBe(1); expect(ngDiags.length).toBe(1);
const {messageText, start, length} = ngDiags[0]; const {messageText, start, length} = ngDiags[0];
@ -391,7 +403,7 @@ describe('diagnostics', () => {
\` \`
}) })
export class AppComponent { export class AppComponent {
fieldCount: number; fieldCount: number = 0;
}`); }`);
const tsDiags = tsLS.getSemanticDiagnostics(fileName); const tsDiags = tsLS.getSemanticDiagnostics(fileName);
expect(tsDiags).toEqual([]); expect(tsDiags).toEqual([]);
@ -401,9 +413,8 @@ describe('diagnostics', () => {
// Issue #15885 // Issue #15885
it('should be able to remove null and undefined from a type', () => { it('should be able to remove null and undefined from a type', () => {
mockHost.overrideOptions(options => { mockHost.overrideOptions({
options.strictNullChecks = true; strictNullChecks: true,
return options;
}); });
const fileName = '/app/app.component.ts'; const fileName = '/app/app.component.ts';
mockHost.override(fileName, ` mockHost.override(fileName, `
@ -441,9 +452,8 @@ describe('diagnostics', () => {
onSubmit(form: NgForm) {} onSubmit(form: NgForm) {}
}`); }`);
mockHost.addScript('/other/files/app/server.ts', 'export class Server {}'); mockHost.addScript('/other/files/app/server.ts', 'export class Server {}');
mockHost.overrideOptions(options => { mockHost.overrideOptions({
options.baseUrl = '/other/files'; baseUrl: '/other/files',
return options;
}); });
const tsDiags = tsLS.getSemanticDiagnostics(fileName); const tsDiags = tsLS.getSemanticDiagnostics(fileName);
expect(tsDiags).toEqual([]); expect(tsDiags).toEqual([]);
@ -568,10 +578,13 @@ describe('diagnostics', () => {
it('should not report errors for valid styleUrls', () => { it('should not report errors for valid styleUrls', () => {
const fileName = '/app/app.component.ts'; const fileName = '/app/app.component.ts';
mockHost.override(fileName, ` mockHost.override(fileName, `
import {Component} from '@angular/core';
@Component({ @Component({
template: '<div></div>',
styleUrls: ['./test.css', './test.css'], styleUrls: ['./test.css', './test.css'],
}) })
export class MyComponent {}`); export class AppComponent {}`);
const diagnostics = ngLS.getDiagnostics(fileName) !; const diagnostics = ngLS.getDiagnostics(fileName) !;
expect(diagnostics.length).toBe(0); expect(diagnostics.length).toBe(0);

View File

@ -8,7 +8,7 @@
import {Component} from '@angular/core'; import {Component} from '@angular/core';
export class Hero { export interface Hero {
id: number; id: number;
name: string; name: string;
} }
@ -30,5 +30,5 @@ export class Hero {
export class AppComponent { export class AppComponent {
title = 'Tour of Heroes'; title = 'Tour of Heroes';
hero: Hero = {id: 1, name: 'Windstorm'}; hero: Hero = {id: 1, name: 'Windstorm'};
private internal: string; private internal: string = 'internal';
} }

View File

@ -44,5 +44,5 @@ export class ExpectNumericType {
template: '{{ (name | lowercase).~{string-pipe}substring }}', template: '{{ (name | lowercase).~{string-pipe}substring }}',
}) })
export class LowercasePipe { export class LowercasePipe {
name: string; name: string = 'name';
} }

View File

@ -29,7 +29,7 @@ export class UnknownPeople {
</div>`, </div>`,
}) })
export class UnknownEven { export class UnknownEven {
people: Person[]; people: Person[] = [];
} }
@Component({ @Component({
@ -39,5 +39,5 @@ export class UnknownEven {
</div>`, </div>`,
}) })
export class UnknownTrackBy { export class UnknownTrackBy {
people: Person[]; people: Person[] = [];
} }

View File

@ -49,21 +49,21 @@ export class NoValueAttribute {
template: '<h1 model="~{attribute-binding-model}test"></h1>', template: '<h1 model="~{attribute-binding-model}test"></h1>',
}) })
export class AttributeBinding { export class AttributeBinding {
test: string; test: string = 'test';
} }
@Component({ @Component({
template: '<h1 [model]="~{property-binding-model}test"></h1>', template: '<h1 [model]="~{property-binding-model}test"></h1>',
}) })
export class PropertyBinding { export class PropertyBinding {
test: string; test: string = 'test';
} }
@Component({ @Component({
template: '<h1 (model)="~{event-binding-model}modelChanged()"></h1>', template: '<h1 (model)="~{event-binding-model}modelChanged()"></h1>',
}) })
export class EventBinding { export class EventBinding {
test: string; test: string = 'test';
modelChanged() {} modelChanged() {}
} }
@ -72,23 +72,23 @@ export class EventBinding {
template: '<h1 [(model)]="~{two-way-binding-model}test"></h1>', template: '<h1 [(model)]="~{two-way-binding-model}test"></h1>',
}) })
export class TwoWayBinding { export class TwoWayBinding {
test: string; test: string = 'test';
} }
@Directive({ @Directive({
selector: '[string-model]', selector: '[string-model]',
}) })
export class StringModel { export class StringModel {
@Input() model: string; @Input() model: string = 'model';
@Output() modelChanged: EventEmitter<string>; @Output() modelChanged: EventEmitter<string> = new EventEmitter();
} }
@Directive({ @Directive({
selector: '[number-model]', selector: '[number-model]',
}) })
export class NumberModel { export class NumberModel {
@Input('inputAlias') model: number; @Input('inputAlias') model: number = 0;
@Output('outputAlias') modelChanged: EventEmitter<number>; @Output('outputAlias') modelChanged: EventEmitter<number> = new EventEmitter();
} }
interface Person { interface Person {
@ -122,7 +122,7 @@ export class ForLetIEqual {
</div>`, </div>`,
}) })
export class ForUsingComponent { export class ForUsingComponent {
people: Person[]; people: Person[] = [];
} }
@Component({ @Component({

View File

@ -0,0 +1,11 @@
{
"//00": "This file is used for IDE only, actual compilation options is in MockTypescriptHost in test_utils.ts",
"compilerOptions": {
"strict": true,
"experimentalDecorators": true,
"baseUrl": "../../../..",
"paths": {
"@angular/*": ["packages/*"]
}
}
}

View File

@ -79,6 +79,17 @@ function loadTourOfHeroes(): ReadonlyMap<string, string> {
} }
const TOH = loadTourOfHeroes(); const TOH = loadTourOfHeroes();
const COMPILER_OPTIONS: Readonly<ts.CompilerOptions> = {
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.CommonJS,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
emitDecoratorMetadata: true,
experimentalDecorators: true,
removeComments: false,
noImplicitAny: false,
lib: ['lib.es2015.d.ts', 'lib.dom.d.ts'],
strict: true,
};
export class MockTypescriptHost implements ts.LanguageServiceHost { export class MockTypescriptHost implements ts.LanguageServiceHost {
private angularPath?: string; private angularPath?: string;
@ -98,16 +109,7 @@ export class MockTypescriptHost implements ts.LanguageServiceHost {
const support = setup(); const support = setup();
this.nodeModulesPath = path.posix.join(support.basePath, 'node_modules'); this.nodeModulesPath = path.posix.join(support.basePath, 'node_modules');
this.angularPath = path.posix.join(this.nodeModulesPath, '@angular'); this.angularPath = path.posix.join(this.nodeModulesPath, '@angular');
this.options = { this.options = COMPILER_OPTIONS;
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.CommonJS,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
emitDecoratorMetadata: true,
experimentalDecorators: true,
removeComments: false,
noImplicitAny: false,
lib: ['lib.es2015.d.ts', 'lib.dom.d.ts'],
};
} }
override(fileName: string, content: string) { override(fileName: string, content: string) {
@ -133,12 +135,12 @@ export class MockTypescriptHost implements ts.LanguageServiceHost {
forgetAngular() { this.angularPath = undefined; } forgetAngular() { this.angularPath = undefined; }
overrideOptions(cb: (options: ts.CompilerOptions) => ts.CompilerOptions) { overrideOptions(options: Partial<ts.CompilerOptions>) {
this.options = cb((Object as any).assign({}, this.options)); this.options = {...this.options, ...options};
this.projectVersion++; this.projectVersion++;
} }
getCompilationSettings(): ts.CompilerOptions { return this.options; } getCompilationSettings(): ts.CompilerOptions { return {...this.options}; }
getProjectVersion(): string { return this.projectVersion.toString(); } getProjectVersion(): string { return this.projectVersion.toString(); }
@ -199,6 +201,7 @@ export class MockTypescriptHost implements ts.LanguageServiceHost {
reset() { reset() {
this.overrides.clear(); this.overrides.clear();
this.overrideDirectory.clear(); this.overrideDirectory.clear();
this.options = COMPILER_OPTIONS;
} }
private getRawFileContent(fileName: string): string|undefined { private getRawFileContent(fileName: string): string|undefined {