-
Notifications
You must be signed in to change notification settings - Fork 235
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(rule): add no-life-cycle-call rule (#559)
- Loading branch information
1 parent
f3a53bd
commit 3e10013
Showing
3 changed files
with
169 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { sprintf } from 'sprintf-js'; | ||
import * as Lint from 'tslint'; | ||
import * as ts from 'typescript'; | ||
import { NgWalker } from './angular/ngWalker'; | ||
|
||
export class Rule extends Lint.Rules.AbstractRule { | ||
public static metadata: Lint.IRuleMetadata = { | ||
ruleName: 'no-life-cycle-call', | ||
type: 'maintainability', | ||
description: 'Disallows explicit calls to lifecycle hooks.', | ||
rationale: 'Explicit calls to lifecycle hooks could be confusing. Invoke lifecycle hooks is the responsability of Angular.', | ||
options: null, | ||
optionsDescription: 'Not configurable.', | ||
typescriptOnly: true, | ||
}; | ||
|
||
static FAILURE_STRING: string = 'Avoid explicitly calls to lifecycle hooks in class "%s"'; | ||
|
||
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { | ||
return this.applyWithWalker(new ExpressionCallMetadataWalker(sourceFile, this.getOptions())); | ||
} | ||
} | ||
|
||
export type LifecycleHooksMethods = | ||
'ngAfterContentChecked' | | ||
'ngAfterContentInit' | | ||
'ngAfterViewChecked' | | ||
'ngAfterViewInit' | | ||
'ngDoCheck' | | ||
'ngOnChanges' | | ||
'ngOnDestroy' | | ||
'ngOnInit'; | ||
|
||
export const lifecycleHooksMethods = new Set<LifecycleHooksMethods>([ | ||
'ngAfterContentChecked', | ||
'ngAfterContentInit', | ||
'ngAfterViewChecked', | ||
'ngAfterViewInit', | ||
'ngDoCheck', | ||
'ngOnChanges', | ||
'ngOnDestroy', | ||
'ngOnInit' | ||
]); | ||
|
||
export class ExpressionCallMetadataWalker extends NgWalker { | ||
visitCallExpression(node: ts.CallExpression): void { | ||
this.validateCallExpression(node); | ||
super.visitCallExpression(node); | ||
} | ||
|
||
private validateCallExpression(node: ts.CallExpression): void { | ||
const name = (node.expression as any).name; | ||
|
||
if (!name || !lifecycleHooksMethods.has(name.text)) { | ||
return; | ||
} | ||
|
||
let currentNode = node as any; | ||
|
||
while (currentNode.parent.parent) { | ||
currentNode = currentNode.parent; | ||
} | ||
|
||
const failureConfig = [Rule.FAILURE_STRING, currentNode.name.text]; | ||
this.addFailureAtNode(node, sprintf.apply(this, failureConfig)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { lifecycleHooksMethods, LifecycleHooksMethods } from '../src/noLifeCycleCallRule'; | ||
import { assertAnnotated, assertSuccess } from './testHelper'; | ||
|
||
type Metadata = 'Component' | 'Directive' | 'Injectable' | 'Pipe'; | ||
|
||
type MetadataPair = { | ||
[key in Metadata]: typeof lifecycleHooksMethods | ||
}; | ||
|
||
type MetadataFakePair = { | ||
[key in Metadata]?: Set<string> | ||
}; | ||
|
||
const className = 'Test'; | ||
const ruleName = 'no-life-cycle-call'; | ||
const metadataPairs: MetadataPair = { | ||
Component: lifecycleHooksMethods, | ||
Directive: lifecycleHooksMethods, | ||
Injectable: new Set<LifecycleHooksMethods>([ | ||
'ngOnDestroy' | ||
]), | ||
Pipe: new Set<LifecycleHooksMethods>([ | ||
'ngOnDestroy' | ||
]) | ||
}; | ||
|
||
const metadataKeys = Object.keys(metadataPairs); | ||
const prefix = 'prefix'; | ||
const suffix = 'Suffix'; | ||
const metadataFakePairs: MetadataFakePair = {}; | ||
for (const metadataKey of metadataKeys) { | ||
metadataFakePairs[metadataKey] = new Set<string>(); | ||
|
||
metadataPairs[metadataKey].forEach(lifecycleHookMethod => { | ||
metadataFakePairs[metadataKey] | ||
.add(`${prefix}${lifecycleHookMethod}`) | ||
.add(`${lifecycleHookMethod}${suffix}`) | ||
.add(`${prefix}${lifecycleHookMethod}${suffix}`); | ||
}); | ||
} | ||
|
||
describe(ruleName, () => { | ||
describe('failure', () => { | ||
for (const metadataKey of metadataKeys) { | ||
describe(metadataKey, () => { | ||
metadataPairs[metadataKey].forEach(lifecycleHookMethod => { | ||
const lifecycleHookMethodCall = `this.${lifecycleHookMethod}()`; | ||
const totalTildes = '~'.repeat(lifecycleHookMethodCall.length); | ||
const source = ` | ||
@${metadataKey}() | ||
class ${className} implements ${lifecycleHookMethod.slice(2)} { | ||
${lifecycleHookMethod}() { } | ||
${className.toLowerCase()}() { | ||
${lifecycleHookMethodCall} | ||
${totalTildes} | ||
} | ||
} | ||
`; | ||
|
||
it(`should fail when explicitly call ${lifecycleHookMethod}`, () => { | ||
assertAnnotated({ | ||
ruleName, | ||
message: `Avoid explicitly calls to lifecycle hooks in class "${className}"`, | ||
source | ||
}); | ||
}); | ||
}); | ||
}); | ||
} | ||
}); | ||
|
||
describe('success', () => { | ||
for (const metadataKey of metadataKeys) { | ||
describe(metadataKey, () => { | ||
metadataFakePairs[metadataKey].forEach(fakeMethod => { | ||
const source = ` | ||
@${metadataKey}() | ||
class ${className} { | ||
${fakeMethod}() { } | ||
${className.toLowerCase()}() { | ||
this.${fakeMethod}(); | ||
} | ||
} | ||
`; | ||
|
||
it(`should pass when call ${fakeMethod} method`, () => { | ||
assertSuccess(ruleName, source); | ||
}); | ||
}); | ||
}); | ||
} | ||
}); | ||
}); |