feat(ivy): throw compilation error when providing undecorated classes (#34460)
Adds a compilation error if the consumer tries to pass in an undecorated class into the `providers` of an `NgModule`, or the `providers`/`viewProviders` arrays of a `Directive`/`Component`. PR Close #34460
This commit is contained in:
@ -89,3 +89,5 @@ export class EventEmitter<T> {
|
||||
export interface QueryList<T>/* implements Iterable<T> */ { [Symbol.iterator]: () => Iterator<T>; }
|
||||
|
||||
export type NgIterable<T> = Array<T>| Iterable<T>;
|
||||
|
||||
export class NgZone {}
|
||||
|
@ -5222,6 +5222,334 @@ export const Foo = Foo__PRE_R3__;
|
||||
});
|
||||
});
|
||||
|
||||
describe('undecorated providers', () => {
|
||||
it('should error when an undecorated class, with a non-trivial constructor, is provided directly in a module',
|
||||
() => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule, NgZone} from '@angular/core';
|
||||
|
||||
class NotAService {
|
||||
constructor(ngZone: NgZone) {}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
providers: [NotAService]
|
||||
})
|
||||
export class SomeModule {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText).toContain('cannot be created via dependency injection');
|
||||
});
|
||||
|
||||
it('should error when an undecorated class is provided via useClass', () => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule, Injectable, NgZone} from '@angular/core';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
class Service {}
|
||||
|
||||
class NotAService {
|
||||
constructor(ngZone: NgZone) {}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
providers: [{provide: Service, useClass: NotAService}]
|
||||
})
|
||||
export class SomeModule {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText).toContain('cannot be created via dependency injection');
|
||||
});
|
||||
|
||||
it('should not error when an undecorated class is provided via useClass with deps', () => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule, Injectable, NgZone} from '@angular/core';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
class Service {}
|
||||
|
||||
class NotAService {
|
||||
constructor(ngZone: NgZone) {}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
providers: [{provide: Service, useClass: NotAService, deps: [NgZone]}]
|
||||
})
|
||||
export class SomeModule {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should error when an undecorated class is provided via an array', () => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule, Injectable, NgZone} from '@angular/core';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
class Service {}
|
||||
|
||||
class NotAService {
|
||||
constructor(ngZone: NgZone) {}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
providers: [Service, [NotAService]]
|
||||
})
|
||||
export class SomeModule {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText).toContain('cannot be created via dependency injection');
|
||||
});
|
||||
|
||||
it('should error when an undecorated class is provided to a directive', () => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule, Directive, NgZone} from '@angular/core';
|
||||
|
||||
class NotAService {
|
||||
constructor(ngZone: NgZone) {}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[some-dir]',
|
||||
providers: [NotAService]
|
||||
})
|
||||
class SomeDirective {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [SomeDirective]
|
||||
})
|
||||
export class SomeModule {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText).toContain('cannot be created via dependency injection');
|
||||
});
|
||||
|
||||
it('should error when an undecorated class is provided to a component', () => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule, Component, NgZone} from '@angular/core';
|
||||
|
||||
class NotAService {
|
||||
constructor(ngZone: NgZone) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'some-comp',
|
||||
template: '',
|
||||
providers: [NotAService]
|
||||
})
|
||||
class SomeComponent {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [SomeComponent]
|
||||
})
|
||||
export class SomeModule {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText).toContain('cannot be created via dependency injection');
|
||||
});
|
||||
|
||||
it('should error when an undecorated class is provided to a component via viewProviders',
|
||||
() => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule, Component, NgZone} from '@angular/core';
|
||||
|
||||
class NotAService {
|
||||
constructor(ngZone: NgZone) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'some-comp',
|
||||
template: '',
|
||||
viewProviders: [NotAService]
|
||||
})
|
||||
class SomeComponent {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [SomeComponent]
|
||||
})
|
||||
export class SomeModule {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText).toContain('cannot be created via dependency injection');
|
||||
});
|
||||
|
||||
it('should not error when a class with a factory is provided', () => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule, Pipe} from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'some-pipe'
|
||||
})
|
||||
class SomePipe {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [SomePipe],
|
||||
providers: [SomePipe]
|
||||
})
|
||||
export class SomeModule {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not error when an NgModule is provided', () => {
|
||||
env.write('test.ts', `
|
||||
import {Injectable, NgModule} from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class Service {}
|
||||
|
||||
@NgModule({
|
||||
})
|
||||
class SomeModule {
|
||||
constructor(dep: Service) {}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
providers: [SomeModule],
|
||||
})
|
||||
export class Module {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not error when an undecorated class from a declaration file is provided', () => {
|
||||
env.write('node_modules/@angular/core/testing/index.d.ts', `
|
||||
export declare class Testability {
|
||||
}
|
||||
`);
|
||||
env.write('test.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
import {Testability} from '@angular/core/testing';
|
||||
|
||||
@NgModule({
|
||||
providers: [Testability]
|
||||
})
|
||||
export class SomeModule {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not error when an undecorated class without a constructor from a declaration file is provided via useClass',
|
||||
() => {
|
||||
env.write('node_modules/@angular/core/testing/index.d.ts', `
|
||||
export declare class Testability {
|
||||
}
|
||||
`);
|
||||
env.write('test.ts', `
|
||||
import {NgModule, Injectable} from '@angular/core';
|
||||
import {Testability} from '@angular/core/testing';
|
||||
|
||||
@Injectable()
|
||||
class TestingService {}
|
||||
|
||||
@NgModule({
|
||||
providers: [{provide: TestingService, useClass: Testability}]
|
||||
})
|
||||
export class SomeModule {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not error if the undecorated class does not have a constructor or the constructor is blank',
|
||||
() => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule, NgZone} from '@angular/core';
|
||||
|
||||
class NoConstructorService {
|
||||
}
|
||||
|
||||
class BlankConstructorService {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
providers: [NoConstructorService, BlankConstructorService]
|
||||
})
|
||||
export class SomeModule {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should error when an undecorated class with a non-trivial constructor in a declaration file is provided via useClass',
|
||||
() => {
|
||||
env.write('node_modules/@angular/core/testing/index.d.ts', `
|
||||
export declare class NgZone {}
|
||||
|
||||
export declare class Testability {
|
||||
constructor(ngZone: NgZone) {}
|
||||
}
|
||||
`);
|
||||
env.write('test.ts', `
|
||||
import {NgModule, Injectable} from '@angular/core';
|
||||
import {Testability} from '@angular/core/testing';
|
||||
|
||||
@Injectable()
|
||||
class TestingService {}
|
||||
|
||||
@NgModule({
|
||||
providers: [{provide: TestingService, useClass: Testability}]
|
||||
})
|
||||
export class SomeModule {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText).toContain('cannot be created via dependency injection');
|
||||
});
|
||||
|
||||
it('should not error when an class with a factory definition and a non-trivial constructor in a declaration file is provided via useClass',
|
||||
() => {
|
||||
env.write('node_modules/@angular/core/testing/index.d.ts', `
|
||||
import * as i0 from '@angular/core';
|
||||
|
||||
export declare class NgZone {}
|
||||
|
||||
export declare class Testability {
|
||||
static ɵfac: i0.ɵɵFactoryDef<Testability>;
|
||||
constructor(ngZone: NgZone) {}
|
||||
}
|
||||
`);
|
||||
env.write('test.ts', `
|
||||
import {NgModule, Injectable} from '@angular/core';
|
||||
import {Testability} from '@angular/core/testing';
|
||||
|
||||
@Injectable()
|
||||
class TestingService {}
|
||||
|
||||
@NgModule({
|
||||
providers: [{provide: TestingService, useClass: Testability}]
|
||||
})
|
||||
export class SomeModule {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(0);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function expectTokenAtPosition<T extends ts.Node>(
|
||||
|
Reference in New Issue
Block a user