UNPKG

3.98 kBPlain TextView Raw
1import * as ts from 'typescript';
2import * as Lint from 'tslint';
3
4import { ErrorTolerantWalker } from './utils/ErrorTolerantWalker';
5import { ExtendedMetadata } from './utils/ExtendedMetadata';
6
7/**
8 * Implementation of the no-map-without-usage rule.
9 */
10export class Rule extends Lint.Rules.AbstractRule {
11 public static metadata: ExtendedMetadata = {
12 ruleName: 'no-map-without-usage',
13 type: 'maintainability', // one of: 'functionality' | 'maintainability' | 'style' | 'typescript'
14 description: 'Prevent Array.prototype.map from being called and results not used.',
15 options: null,
16 optionsDescription: '',
17 optionExamples: [], //Remove this property if the rule has no options
18 typescriptOnly: false,
19 issueClass: 'Non-SDL', // one of: 'SDL' | 'Non-SDL' | 'Ignored'
20 issueType: 'Warning', // one of: 'Error' | 'Warning'
21 severity: 'Low', // one of: 'Critical' | 'Important' | 'Moderate' | 'Low'
22 level: 'Opportunity for Excellence', // one of 'Mandatory' | 'Opportunity for Excellence'
23 group: 'Correctness', // one of 'Ignored' | 'Security' | 'Correctness' | 'Clarity' | 'Whitespace' | 'Configurable' | 'Deprecated'
24 commonWeaknessEnumeration: '', // if possible, please map your rule to a CWE (see cwe_descriptions.json and https://cwe.mitre.org)
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
35class 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}