Skip to content

Commit

Permalink
fix(rule): handle NgModule with both contextual-lifecycle and context…
Browse files Browse the repository at this point in the history
…ual-decorators rules (#790)
  • Loading branch information
wKoza authored and mgechev committed Mar 18, 2019
1 parent 5629397 commit cedfa2e
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 4 deletions.
7 changes: 6 additions & 1 deletion src/contextualLifecycleRule.ts
Expand Up @@ -14,7 +14,7 @@ import {
MetadataTypeKeys,
MetadataTypes
} from './util/utils';
import { InjectableMetadata, PipeMetadata } from './angular';
import { InjectableMetadata, ModuleMetadata, PipeMetadata } from './angular';

interface FailureParameters {
readonly className: string;
Expand Down Expand Up @@ -56,6 +56,11 @@ class ContextualLifecycleWalker extends NgWalker {
super.visitNgPipe(metadata);
}

protected visitNgModule(metadata: ModuleMetadata) {
this.validateDecorator(metadata, METADATA_TYPE_LIFECYCLE_MAPPER.NgModule);
super.visitNgModule(metadata);
}

private validateDecorator(metadata: PipeMetadata, allowedMethods: ReadonlySet<LifecycleMethodKeys>): void {
const className = getClassName(metadata.controller)!;

Expand Down
9 changes: 6 additions & 3 deletions src/util/utils.ts
Expand Up @@ -61,7 +61,8 @@ export enum MetadataTypes {
Component = 'Component',
Directive = 'Directive',
Injectable = 'Injectable',
Pipe = 'Pipe'
Pipe = 'Pipe',
NgModule = 'NgModule'
}

export type DecoratorKeys = keyof typeof Decorators;
Expand All @@ -84,13 +85,15 @@ export const METADATA_TYPE_DECORATOR_MAPPER: MetadataTypeDecoratorMapper = {
Component: DECORATORS,
Directive: DECORATORS,
Injectable: new Set<DecoratorKeys>([]),
Pipe: new Set<DecoratorKeys>([])
Pipe: new Set<DecoratorKeys>([]),
NgModule: new Set<DecoratorKeys>([])
};
export const METADATA_TYPE_LIFECYCLE_MAPPER: MetadataTypeLifecycleMapper = {
Component: LIFECYCLE_METHODS,
Directive: LIFECYCLE_METHODS,
Injectable: new Set<LifecycleMethodKeys>([LifecycleMethods.ngOnDestroy]),
Pipe: new Set<LifecycleMethodKeys>([LifecycleMethods.ngOnDestroy])
Pipe: new Set<LifecycleMethodKeys>([LifecycleMethods.ngOnDestroy]),
NgModule: new Set<LifecycleMethodKeys>([])
};

export const getClassName = (node: Node): string | undefined => {
Expand Down
157 changes: 157 additions & 0 deletions test/contextualDecoratorRule.spec.ts
Expand Up @@ -165,6 +165,163 @@ describe(ruleName, () => {
});
});

describe('NgModule', () => {
it('should fail if a property is decorated with @ContentChild() decorator', () => {
const source = `
@NgModule()
class Test {
@ContentChild(Pane) pane: Pane;
~~~~~~~~~~~~~~~~~~~
}
`;
assertAnnotated({
message: getFailureMessage({
className: 'Test',
decoratorName: Decorators.ContentChild,
metadataType: MetadataTypes.NgModule
}),
ruleName,
source
});
});

it('should fail if a property is decorated with @ContentChildren() decorator', () => {
const source = `
@NgModule()
class Test {
@ContentChildren(Pane, { descendants: true }) arbitraryNestedPanes: QueryList<Pane>;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
`;
assertAnnotated({
message: getFailureMessage({
className: 'Test',
decoratorName: Decorators.ContentChildren,
metadataType: MetadataTypes.NgModule
}),
ruleName,
source
});
});

it('should fail if a property is decorated with @HostBinding() decorator', () => {
const source = `
@NgModule()
class Test {
@HostBinding('class.card-outline') private isCardOutline: boolean;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
`;
assertAnnotated({
message: getFailureMessage({
className: 'Test',
decoratorName: Decorators.HostBinding,
metadataType: MetadataTypes.NgModule
}),
ruleName,
source
});
});

it('should fail if a method is decorated with @HostListener() decorator', () => {
const source = `
@NgModule()
class Test {
@HostListener('mouseover')
~~~~~~~~~~~~~~~~~~~~~~~~~~
mouseOver() {
console.log('mouseOver');
}
}
`;
assertAnnotated({
message: getFailureMessage({
className: 'Test',
decoratorName: Decorators.HostListener,
metadataType: MetadataTypes.NgModule
}),
ruleName,
source
});
});

it('should fail if a property is decorated with @Input() decorator', () => {
const source = `
@NgModule()
class Test {
@Input() label: string;
~~~~~~~~
}
`;
assertAnnotated({
message: getFailureMessage({
className: 'Test',
decoratorName: Decorators.Input,
metadataType: MetadataTypes.NgModule
}),
ruleName,
source
});
});

it('should fail if a property is decorated with @Output() decorator', () => {
const source = `
@NgModule()
class Test {
@Output() emitter = new EventEmitter<void>();
~~~~~~~~~
}
`;
assertAnnotated({
message: getFailureMessage({
className: 'Test',
decoratorName: Decorators.Output,
metadataType: MetadataTypes.NgModule
}),
ruleName,
source
});
});

it('should fail if a property is decorated with @ViewChild() decorator', () => {
const source = `
@NgModule()
class Test {
@ViewChild(Pane) pane: Pane;
~~~~~~~~~~~~~~~~
}
`;
assertAnnotated({
message: getFailureMessage({
className: 'Test',
decoratorName: Decorators.ViewChild,
metadataType: MetadataTypes.NgModule
}),
ruleName,
source
});
});

it('should fail if a property is decorated with @ViewChildren() decorator', () => {
const source = `
@NgModule()
class Test {
@ViewChildren(Pane) panes: QueryList<Pane>;
~~~~~~~~~~~~~~~~~~~
}
`;
assertAnnotated({
message: getFailureMessage({
className: 'Test',
decoratorName: Decorators.ViewChildren,
metadataType: MetadataTypes.NgModule
}),
ruleName,
source
});
});
});

describe('Pipe', () => {
it('should fail if a property is decorated with @ContentChild() decorator', () => {
const source = `
Expand Down
162 changes: 162 additions & 0 deletions test/contextualLifecycleRule.spec.ts
Expand Up @@ -150,6 +150,168 @@ describe(ruleName, () => {
});
});

describe('NgModule', () => {
it('should fail if ngAfterContentChecked() method is present', () => {
const source = `
@NgModule()
class Test {
ngAfterContentChecked() { console.log('AfterContentChecked'); }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
`;
const message = getFailureMessage({
className: 'Test',
metadataType: MetadataTypes.NgModule,
methodName: LifecycleMethods.ngAfterContentChecked
});
assertAnnotated({
message,
ruleName,
source
});
});

it('should fail if ngAfterContentInit() method is present', () => {
const source = `
@NgModule()
class Test {
ngAfterContentInit() { console.log('AfterContentInit'); }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
`;
const message = getFailureMessage({
className: 'Test',
metadataType: MetadataTypes.NgModule,
methodName: LifecycleMethods.ngAfterContentInit
});
assertAnnotated({
message,
ruleName,
source
});
});

it('should fail if ngAfterViewChecked() method is present', () => {
const source = `
@NgModule()
class Test {
ngAfterViewChecked() { console.log('AfterViewChecked'); }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
`;
const message = getFailureMessage({
className: 'Test',
metadataType: MetadataTypes.NgModule,
methodName: LifecycleMethods.ngAfterViewChecked
});
assertAnnotated({
message,
ruleName,
source
});
});

it('should fail if ngAfterViewInit() method is present', () => {
const source = `
@NgModule()
class Test {
ngAfterViewInit() { console.log('AfterViewInit'); }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
`;
const message = getFailureMessage({
className: 'Test',
metadataType: MetadataTypes.NgModule,
methodName: LifecycleMethods.ngAfterViewInit
});
assertAnnotated({
message,
ruleName,
source
});
});

it('should fail if ngDoCheck() method is present', () => {
const source = `
@NgModule()
class Test {
ngDoCheck() { console.log('DoCheck'); }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
`;
const message = getFailureMessage({
className: 'Test',
metadataType: MetadataTypes.NgModule,
methodName: LifecycleMethods.ngDoCheck
});
assertAnnotated({
message,
ruleName,
source
});
});

it('should fail if ngOnChanges() method is present', () => {
const source = `
@NgModule()
class Test {
ngOnChanges() { console.log('OnChanges'); }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
`;
const message = getFailureMessage({
className: 'Test',
metadataType: MetadataTypes.NgModule,
methodName: LifecycleMethods.ngOnChanges
});
assertAnnotated({
message,
ruleName,
source
});
});

it('should fail if ngOnInit() method is present', () => {
const source = `
@NgModule()
class Test {
ngOnInit() { console.log('OnInit'); }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
`;
const message = getFailureMessage({
className: 'Test',
metadataType: MetadataTypes.NgModule,
methodName: LifecycleMethods.ngOnInit
});
assertAnnotated({
message,
ruleName,
source
});
});

it('should fail if ngOnDestroy() method is present', () => {
const source = `
@NgModule()
class Test {
ngOnDestroy() { console.log('OnDestroy'); }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
`;
const message = getFailureMessage({
className: 'Test',
metadataType: MetadataTypes.NgModule,
methodName: LifecycleMethods.ngOnDestroy
});
assertAnnotated({
message,
ruleName,
source
});
});
});

describe('Pipe', () => {
it('should fail if ngAfterContentChecked() method is present', () => {
const source = `
Expand Down

0 comments on commit cedfa2e

Please sign in to comment.