Skip to content

Instantly share code, notes, and snippets.

@sandipchitale
Last active January 5, 2020 22:56
Show Gist options
  • Select an option

  • Save sandipchitale/52b5f4696f5cc83ded3dc5ce475750c2 to your computer and use it in GitHub Desktop.

Select an option

Save sandipchitale/52b5f4696f5cc83ded3dc5ce475750c2 to your computer and use it in GitHub Desktop.
TSLInt rule that ensures that a @component has constructor, ngOnInit and ngOnDestroy in correct order.
/**
* TSLInt rule that ensures that a @Component has constructor, ngOnInit and ngOnDestroy in correct order.
*/
import {
SourceFile, ClassDeclaration, MethodDeclaration,
createNodeArray,
isCallExpression,
isIdentifier,
Decorator,
SyntaxKind
} from 'typescript';
import { IRuleMetadata, RuleFailure, Rules, IOptions } from 'tslint/lib';
import { NgWalker } from 'codelyzer/angular/ngWalker';
export class Rule extends Rules.AbstractRule {
static readonly metadata: IRuleMetadata = {
ruleName: 'component-lifecycle-method-order',
type: 'maintainability',
description: `Ensures that component method order is first constructor(), ngInit(), ..., last ngDestroy().`,
options: null,
optionsDescription: 'Not configurable',
rationale: `InfoArchive Coding Convention`,
typescriptOnly: true,
};
static readonly FAILURE_STRING = 'Component method order should be first constructor(), ngInit(), ..., last ngDestroy().';
apply(sourceFile: SourceFile): RuleFailure[] {
return this.applyWithWalker(new ComponentWalker(sourceFile, this.getOptions()));
}
}
class ComponentWalker extends NgWalker {
map = {
};
constructor(sourceFile: SourceFile,
_originalOptions: IOptions) {
super(sourceFile, _originalOptions);
}
visitClassDeclaration(classDeclaration: ClassDeclaration): void {
createNodeArray(classDeclaration.decorators).forEach((decorator) => {
// Make sure the class has @Component decorator
if ('Component' === this.getDecoratorName(decorator)) {
// Go thru members and record the order of constructor and methods
const members = createNodeArray(classDeclaration.members);
const order = [];
for (let i = 0; i < members.length; i++) {
if (members[i].kind === SyntaxKind.Constructor) {
order.push('constructor');
} else if (members[i].kind === SyntaxKind.MethodDeclaration) {
if (members[i].name) {
order.push(members[i].name.getText());
}
}
}
// Get the ordinal of constructor, ngOnInit and ngOnDestroy
const cIndex = order.indexOf('constructor');
const niIndex = order.indexOf('ngOnInit');
const ndIndex = order.indexOf('ngOnDestroy');
if ((cIndex === -1 || cIndex === 0) &&
(niIndex === -1 || (cIndex === -1 && niIndex === 0) || (cIndex === 0 && niIndex === 1)) &&
(ndIndex === -1 || ndIndex === order.length - 1)) {
// Order is correct
} else {
this.addFailureFromStartToEnd(
classDeclaration.name.getStart(),
classDeclaration.name.getEnd(),
Rule.FAILURE_STRING);
}
}
});
super.visitClassDeclaration(classDeclaration);
}
private getDecoratorName(decorator: Decorator): string | undefined {
return isCallExpression(decorator.expression) && isIdentifier(decorator.expression.expression)
? decorator.expression.expression.text
: undefined;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment