1 | import * as ts from 'typescript';
|
2 | import * as Lint from 'tslint';
|
3 |
|
4 | import { ErrorTolerantWalker } from './utils/ErrorTolerantWalker';
|
5 | import { ExtendedMetadata } from './utils/ExtendedMetadata';
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | export class Rule extends Lint.Rules.AbstractRule {
|
11 | public static metadata: ExtendedMetadata = {
|
12 | ruleName: 'no-for-each-push',
|
13 | type: 'maintainability',
|
14 | description: 'Enforce using Array.prototype.map instead of Array.prototype.forEach and Array.prototype.push.',
|
15 | options: null,
|
16 | optionsDescription: '',
|
17 | typescriptOnly: true,
|
18 | issueClass: 'Non-SDL',
|
19 | issueType: 'Warning',
|
20 | severity: 'Important',
|
21 | level: 'Opportunity for Excellence',
|
22 | group: 'Correctness',
|
23 | recommendation: 'true,',
|
24 | commonWeaknessEnumeration: '',
|
25 | };
|
26 |
|
27 | public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
|
28 | return this.applyWithWalker(new NoForeachPushRuleWalker(sourceFile, this.getOptions()));
|
29 | }
|
30 |
|
31 | public static FAILURE_STRING: string =
|
32 | 'Do not use Array.prototype.push inside of Array.prototype.forEach. ' + 'Use Array.prototype.map instead to replace both.';
|
33 | }
|
34 |
|
35 | class NoForeachPushRuleWalker extends ErrorTolerantWalker {
|
36 | protected visitPropertyAccessExpression(node: ts.PropertyAccessExpression): void {
|
37 | this.checkAndReport(node);
|
38 | super.visitPropertyAccessExpression(node);
|
39 | }
|
40 |
|
41 | private checkAndReport(node: ts.PropertyAccessExpression): void {
|
42 | const isCallExpression = ts.isCallExpression(node.parent);
|
43 | const isForEach = node.name.text === 'forEach';
|
44 |
|
45 | if (isCallExpression && isForEach) {
|
46 | if (this.doesCallPush(node)) {
|
47 | this.addFailureAtNode(node.parent, Rule.FAILURE_STRING);
|
48 | }
|
49 | }
|
50 | }
|
51 |
|
52 | private doesCallPush(node: ts.PropertyAccessExpression) {
|
53 | const walker = new PushCallWalker();
|
54 | walker.walk(node.parent);
|
55 | return walker.isFound;
|
56 | }
|
57 | }
|
58 |
|
59 | class PushCallWalker extends Lint.SyntaxWalker {
|
60 | private foundPush: boolean = false;
|
61 | private foundIf: boolean = false;
|
62 |
|
63 | protected visitPropertyAccessExpression(node: ts.PropertyAccessExpression): void {
|
64 | const isCallExpression = ts.isCallExpression(node.parent);
|
65 | const isPush = node.name.text === 'push';
|
66 |
|
67 | if (isCallExpression && isPush) {
|
68 | this.foundPush = true;
|
69 | return;
|
70 | }
|
71 | super.visitPropertyAccessExpression(node);
|
72 | }
|
73 |
|
74 | protected visitIfStatement(): void {
|
75 | this.foundIf = true;
|
76 | return;
|
77 | }
|
78 |
|
79 | public get isFound(): boolean {
|
80 | return !this.foundIf && this.foundPush;
|
81 | }
|
82 | }
|