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:
parent
9e7aa60ae7
commit
28358b6395
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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';
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
||||||
}
|
}
|
||||||
|
|
|
@ -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[] = [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue