forked from microsoft/tslint-microsoft-contrib
-
Notifications
You must be signed in to change notification settings - Fork 1
/
preferArrayLiteralRule.ts
130 lines (111 loc) · 5.05 KB
/
preferArrayLiteralRule.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import * as ts from 'typescript';
import * as Lint from 'tslint';
import * as tsutils from 'tsutils';
import { AstUtils } from './utils/AstUtils';
import { ExtendedMetadata } from './utils/ExtendedMetadata';
import { isObject } from './utils/TypeGuard';
// undefined for case when function/constructor is called directly without namespace
const RESTRICTED_NAMESPACES = [undefined, 'window', 'global', 'globalThis'];
function inRestrictedNamespace(node: ts.NewExpression | ts.CallExpression): boolean {
const functionTarget = AstUtils.getFunctionTarget(node);
return RESTRICTED_NAMESPACES.indexOf(functionTarget) > -1;
}
type InvocationType = 'constructor' | 'function';
interface Options {
allowSingleArgument: boolean;
allowTypeParameters: boolean;
}
export class Rule extends Lint.Rules.OptionallyTypedRule {
public static metadata: ExtendedMetadata = {
ruleName: 'prefer-array-literal',
type: 'maintainability',
description: 'Use array literal syntax when declaring or instantiating array types.',
options: null, // tslint:disable-line:no-null-keyword
optionsDescription: '',
typescriptOnly: true,
issueClass: 'Non-SDL',
issueType: 'Warning',
severity: 'Moderate',
level: 'Opportunity for Excellence',
group: 'Clarity',
commonWeaknessEnumeration: '398, 710'
};
public static GENERICS_FAILURE_STRING: string = 'Replace generic-typed Array with array literal: ';
public static getReplaceFailureString = (type: InvocationType, nodeText: string) =>
`Replace Array ${type} with an array literal: ${nodeText}`;
public static getSingleParamFailureString = (type: InvocationType, nodeText: string) =>
`To create an array of given length you should use non-negative integer. Otherwise replace Array ${type} with an array literal: ${nodeText}`;
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithProgram(sourceFile, /* program */ undefined);
}
public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program | undefined): Lint.RuleFailure[] {
return this.applyWithFunction(
sourceFile,
walk,
this.parseOptions(this.getOptions()),
program ? program.getTypeChecker() : undefined
);
}
private parseOptions(options: Lint.IOptions): Options {
let allowSingleArgument: boolean = false;
let allowTypeParameters: boolean = false;
let ruleOptions: any[] = [];
if (options.ruleArguments instanceof Array) {
ruleOptions = options.ruleArguments;
}
if (options instanceof Array) {
ruleOptions = options;
}
ruleOptions.forEach((opt: unknown) => {
if (isObject(opt)) {
allowSingleArgument = opt['allow-single-argument'] === true;
allowTypeParameters = opt['allow-type-parameters'] === true;
}
});
return {
allowSingleArgument,
allowTypeParameters
};
}
}
function walk(ctx: Lint.WalkContext<Options>, checker: ts.TypeChecker | undefined) {
const { allowTypeParameters, allowSingleArgument } = ctx.options;
function checkExpression(type: InvocationType, node: ts.CallExpression | ts.NewExpression): void {
const functionName = AstUtils.getFunctionName(node);
if (functionName === 'Array' && inRestrictedNamespace(node)) {
const callArguments = node.arguments;
if (!allowSingleArgument || !callArguments || callArguments.length !== 1) {
const failureString = Rule.getReplaceFailureString(type, node.getText());
ctx.addFailureAt(node.getStart(), node.getWidth(), failureString);
} else {
// When typechecker is not available - allow any call with single expression
if (checker) {
const argument = callArguments[0];
const argumentType = checker.getTypeAtLocation(argument);
if (!tsutils.isTypeAssignableToNumber(checker, argumentType)) {
const failureString = Rule.getSingleParamFailureString(type, node.getText());
ctx.addFailureAt(node.getStart(), node.getWidth(), failureString);
}
}
}
}
}
function cb(node: ts.Node): void {
if (tsutils.isTypeReferenceNode(node)) {
if (!allowTypeParameters) {
if ((<ts.Identifier>node.typeName).text === 'Array') {
const failureString = Rule.GENERICS_FAILURE_STRING + node.getText();
ctx.addFailureAt(node.getStart(), node.getWidth(), failureString);
}
}
}
if (tsutils.isNewExpression(node)) {
checkExpression('constructor', node);
}
if (tsutils.isCallExpression(node)) {
checkExpression('function', node);
}
return ts.forEachChild(node, cb);
}
return ts.forEachChild(ctx.sourceFile, cb);
}