diff --git a/src/noLifeCycleCallRule.ts b/src/noLifeCycleCallRule.ts index a9ef60bc0..a29b77acd 100644 --- a/src/noLifeCycleCallRule.ts +++ b/src/noLifeCycleCallRule.ts @@ -14,7 +14,7 @@ export class Rule extends Lint.Rules.AbstractRule { typescriptOnly: true, }; - static FAILURE_STRING: string = 'Avoid explicitly calls to lifecycle hooks in class "%s"'; + static FAILURE_STRING: string = 'Avoid explicit calls to lifecycle hooks.'; public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithWalker(new ExpressionCallMetadataWalker(sourceFile, this.getOptions())); @@ -49,19 +49,14 @@ export class ExpressionCallMetadataWalker extends NgWalker { } private validateCallExpression(node: ts.CallExpression): void { - const name = (node.expression as any).name; + const name = ts.isPropertyAccessExpression(node.expression) ? node.expression.name : undefined; + const expression = ts.isPropertyAccessExpression(node.expression) ? node.expression.expression : undefined; - if (!name || !lifecycleHooksMethods.has(name.text)) { - return; - } - - let currentNode = node as any; + const isSuperCall = expression && expression.kind === ts.SyntaxKind.SuperKeyword; + const isLifecycleCall = name && ts.isIdentifier(name) && lifecycleHooksMethods.has(name.text as any); - while (currentNode.parent.parent) { - currentNode = currentNode.parent; + if (isLifecycleCall && !isSuperCall) { + this.addFailureAtNode(node, Rule.FAILURE_STRING); } - - const failureConfig = [Rule.FAILURE_STRING, currentNode.name.text]; - this.addFailureAtNode(node, sprintf.apply(this, failureConfig)); } } diff --git a/test/noLifeCycleCallRule.spec.ts b/test/noLifeCycleCallRule.spec.ts index fc3182125..9d8bdb176 100644 --- a/test/noLifeCycleCallRule.spec.ts +++ b/test/noLifeCycleCallRule.spec.ts @@ -58,16 +58,39 @@ describe(ruleName, () => { } `; - it(`should fail when explicitly call ${lifecycleHookMethod}`, () => { + it(`should fail when explicitly calling ${lifecycleHookMethodCall}`, () => { assertAnnotated({ ruleName, - message: `Avoid explicitly calls to lifecycle hooks in class "${className}"`, + message: 'Avoid explicit calls to lifecycle hooks.', source }); }); }); }); } + + lifecycleHooksMethods.forEach(lifecycleHookMethod => { + const lifecycleHookMethodCall = `fixture.componentInstance.${lifecycleHookMethod}()`; + const totalTildes = '~'.repeat(lifecycleHookMethodCall.length); + const source = ` + it('should work', () => { + // test code... + + ${lifecycleHookMethodCall} + ${totalTildes} + + // more test code... + }) + `; + + it(`should fail when explicitly calling ${lifecycleHookMethod} outside of a class`, () => { + assertAnnotated({ + ruleName, + message: `Avoid explicit calls to lifecycle hooks.`, + source + }); + }); + }); }); describe('success', () => { @@ -85,7 +108,28 @@ describe(ruleName, () => { } `; - it(`should pass when call ${fakeMethod} method`, () => { + it(`should pass when calling ${fakeMethod} method`, () => { + assertSuccess(ruleName, source); + }); + }); + }); + } + + // call super lifecycle hook + for (const metadataKey of metadataKeys) { + describe(metadataKey, () => { + metadataPairs[metadataKey].forEach(lifecycleHookMethod => { + const lifecycleHookMethodCall = `super.${lifecycleHookMethod}()`; + const source = ` + @${metadataKey}() + class ${className} implements ${lifecycleHookMethod.slice(2)} { + ${lifecycleHookMethod}() { + ${lifecycleHookMethodCall} + } + } + `; + + it(`should pass when explicitly calling ${lifecycleHookMethodCall}`, () => { assertSuccess(ruleName, source); }); });