/
no-unsafe-member-access.ts
139 lines (124 loc) · 4.36 KB
/
no-unsafe-member-access.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
131
132
133
134
135
136
137
138
139
import {
TSESTree,
AST_NODE_TYPES,
} from '@typescript-eslint/experimental-utils';
import * as tsutils from 'tsutils';
import * as util from '../util';
import { getThisExpression } from '../util';
const enum State {
Unsafe = 1,
Safe = 2,
}
export default util.createRule({
name: 'no-unsafe-member-access',
meta: {
type: 'problem',
docs: {
description: 'Disallows member access on any typed variables',
category: 'Possible Errors',
recommended: 'error',
requiresTypeChecking: true,
},
messages: {
unsafeMemberExpression:
'Unsafe member access {{property}} on an `any` value.',
unsafeThisMemberExpression: [
'Unsafe member access {{property}} on an `any` value. `this` is typed as `any`.',
'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.',
].join('\n'),
unsafeComputedMemberAccess:
'Computed name {{property}} resolves to an any value.',
},
schema: [],
},
defaultOptions: [],
create(context) {
const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context);
const checker = program.getTypeChecker();
const compilerOptions = program.getCompilerOptions();
const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled(
compilerOptions,
'noImplicitThis',
);
const sourceCode = context.getSourceCode();
const stateCache = new Map<TSESTree.Node, State>();
function checkMemberExpression(node: TSESTree.MemberExpression): State {
const cachedState = stateCache.get(node);
if (cachedState) {
return cachedState;
}
if (node.object.type === AST_NODE_TYPES.MemberExpression) {
const objectState = checkMemberExpression(node.object);
if (objectState === State.Unsafe) {
// if the object is unsafe, we know this will be unsafe as well
// we don't need to report, as we have already reported on the inner member expr
stateCache.set(node, objectState);
return objectState;
}
}
const tsNode = esTreeNodeToTSNodeMap.get(node.object);
const type = checker.getTypeAtLocation(tsNode);
const state = util.isTypeAnyType(type) ? State.Unsafe : State.Safe;
stateCache.set(node, state);
if (state === State.Unsafe) {
const propertyName = sourceCode.getText(node.property);
let messageId: 'unsafeMemberExpression' | 'unsafeThisMemberExpression' =
'unsafeMemberExpression';
if (!isNoImplicitThis) {
// `this.foo` or `this.foo[bar]`
const thisExpression = getThisExpression(node);
if (
thisExpression &&
util.isTypeAnyType(
util.getConstrainedTypeAtLocation(
checker,
esTreeNodeToTSNodeMap.get(thisExpression),
),
)
) {
messageId = 'unsafeThisMemberExpression';
}
}
context.report({
node,
messageId,
data: {
property: node.computed ? `[${propertyName}]` : `.${propertyName}`,
},
});
}
return state;
}
return {
// ignore MemberExpression if it's parent is TSClassImplements or TSInterfaceHeritage
':not(TSClassImplements, TSInterfaceHeritage) > MemberExpression': checkMemberExpression,
'MemberExpression[computed = true] > *.property'(
node: TSESTree.Expression,
): void {
if (
// x[1]
node.type === AST_NODE_TYPES.Literal ||
// x[1++] x[++x] etc
// FUN FACT - **all** update expressions return type number, regardless of the argument's type,
// because JS engines return NaN if there the argument is not a number.
node.type === AST_NODE_TYPES.UpdateExpression
) {
// perf optimizations - literals can obviously never be `any`
return;
}
const tsNode = esTreeNodeToTSNodeMap.get(node);
const type = checker.getTypeAtLocation(tsNode);
if (util.isTypeAnyType(type)) {
const propertyName = sourceCode.getText(node);
context.report({
node,
messageId: 'unsafeComputedMemberAccess',
data: {
property: `[${propertyName}]`,
},
});
}
},
};
},
});