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-map-without-usage',
|
13 | type: 'maintainability',
|
14 | description: 'Prevent Array.prototype.map from being called and results not used.',
|
15 | options: null,
|
16 | optionsDescription: '',
|
17 | optionExamples: [],
|
18 | typescriptOnly: false,
|
19 | issueClass: 'Non-SDL',
|
20 | issueType: 'Warning',
|
21 | severity: 'Low',
|
22 | level: 'Opportunity for Excellence',
|
23 | group: 'Correctness',
|
24 | commonWeaknessEnumeration: '',
|
25 | };
|
26 |
|
27 | public static FAILURE_STRING: string =
|
28 | 'Return value from Array.prototype.map should be assigned to a variable. ' + 'Consider using Array.prototype.forEach instead.';
|
29 |
|
30 | public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
|
31 | return this.applyWithWalker(new NoMapWithoutAssignmentRuleWalker(sourceFile, this.getOptions()));
|
32 | }
|
33 | }
|
34 |
|
35 | class NoMapWithoutAssignmentRuleWalker 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 | if (this.isMapCall(node) && !this.isAssignment(node) && !this.isUsed(node)) {
|
43 | this.addFailureAtNode(node, Rule.FAILURE_STRING);
|
44 | }
|
45 | }
|
46 |
|
47 | private isMapCall(node: ts.PropertyAccessExpression): boolean {
|
48 | const isCallExpression = ts.isCallExpression(node.parent);
|
49 | const isMap = node.name.text === 'map';
|
50 | return isCallExpression && isMap;
|
51 | }
|
52 |
|
53 | private isAssignment(node: ts.PropertyAccessExpression): boolean {
|
54 | const { parent: parent1 } = node;
|
55 | if (parent1 && ts.isCallExpression(parent1)) {
|
56 | const { parent: parent2 } = parent1;
|
57 | const parentIsAssignment =
|
58 | ts.isPropertyAssignment(parent2) ||
|
59 | ts.isVariableDeclaration(parent2) ||
|
60 | (ts.isBinaryExpression(parent2) && parent2.operatorToken.kind === ts.SyntaxKind.FirstAssignment);
|
61 | if (parentIsAssignment) {
|
62 | return true;
|
63 | }
|
64 | }
|
65 | return false;
|
66 | }
|
67 |
|
68 | private isUsed(node: ts.PropertyAccessExpression): boolean {
|
69 | const { parent: parent1 } = node;
|
70 | if (parent1 && ts.isCallExpression(parent1)) {
|
71 | const { parent: parent2 } = parent1;
|
72 | if (this.parentUsesNode(parent2)) {
|
73 | return true;
|
74 | }
|
75 | }
|
76 | return false;
|
77 | }
|
78 |
|
79 | private parentUsesNode(parent?: ts.Node) {
|
80 | return (
|
81 | parent &&
|
82 | (ts.isPropertyAccessExpression(parent) ||
|
83 | ts.isPropertyDeclaration(parent) ||
|
84 | ts.isReturnStatement(parent) ||
|
85 | ts.isCallOrNewExpression(parent) ||
|
86 | ts.isSpreadElement(parent) ||
|
87 | ts.isJsxExpression(parent) ||
|
88 | ts.isConditionalExpression(parent) ||
|
89 | ts.isArrayLiteralExpression(parent) ||
|
90 | ts.isBinaryExpression(parent) ||
|
91 | ts.isArrowFunction(parent))
|
92 | );
|
93 | }
|
94 | }
|