1 | "use strict";
|
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3 | if (k2 === undefined) k2 = k;
|
4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
5 | }) : (function(o, m, k, k2) {
|
6 | if (k2 === undefined) k2 = k;
|
7 | o[k2] = m[k];
|
8 | }));
|
9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
10 | Object.defineProperty(o, "default", { enumerable: true, value: v });
|
11 | }) : function(o, v) {
|
12 | o["default"] = v;
|
13 | });
|
14 | var __importStar = (this && this.__importStar) || function (mod) {
|
15 | if (mod && mod.__esModule) return mod;
|
16 | var result = {};
|
17 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
18 | __setModuleDefault(result, mod);
|
19 | return result;
|
20 | };
|
21 | Object.defineProperty(exports, "__esModule", { value: true });
|
22 | const utils_1 = require("@typescript-eslint/utils");
|
23 | const tsutils = __importStar(require("tsutils"));
|
24 | const ts = __importStar(require("typescript"));
|
25 | const util = __importStar(require("../util"));
|
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 | const nativelyNotBoundMembers = new Set([
|
53 | 'Promise.all',
|
54 | 'Promise.race',
|
55 | 'Promise.resolve',
|
56 | 'Promise.reject',
|
57 | 'Promise.allSettled',
|
58 | 'Object.defineProperties',
|
59 | 'Object.defineProperty',
|
60 | 'Reflect.defineProperty',
|
61 | 'Reflect.deleteProperty',
|
62 | 'Reflect.get',
|
63 | 'Reflect.getOwnPropertyDescriptor',
|
64 | 'Reflect.getPrototypeOf',
|
65 | 'Reflect.has',
|
66 | 'Reflect.isExtensible',
|
67 | 'Reflect.ownKeys',
|
68 | 'Reflect.preventExtensions',
|
69 | 'Reflect.set',
|
70 | 'Reflect.setPrototypeOf',
|
71 | ]);
|
72 | const SUPPORTED_GLOBALS = [
|
73 | 'Number',
|
74 | 'Object',
|
75 | 'String',
|
76 | 'RegExp',
|
77 | 'Symbol',
|
78 | 'Array',
|
79 | 'Proxy',
|
80 | 'Date',
|
81 | 'Infinity',
|
82 | 'Atomics',
|
83 | 'Reflect',
|
84 | 'console',
|
85 | 'Math',
|
86 | 'JSON',
|
87 | 'Intl',
|
88 | ];
|
89 | const nativelyBoundMembers = SUPPORTED_GLOBALS.map(namespace => {
|
90 | if (!(namespace in global)) {
|
91 |
|
92 |
|
93 | return [];
|
94 | }
|
95 | const object = global[namespace];
|
96 | return Object.getOwnPropertyNames(object)
|
97 | .filter(name => !name.startsWith('_') &&
|
98 | typeof object[name] === 'function')
|
99 | .map(name => `${namespace}.${name}`);
|
100 | })
|
101 | .reduce((arr, names) => arr.concat(names), [])
|
102 | .filter(name => !nativelyNotBoundMembers.has(name));
|
103 | const isNotImported = (symbol, currentSourceFile) => {
|
104 | const { valueDeclaration } = symbol;
|
105 | if (!valueDeclaration) {
|
106 |
|
107 | return false;
|
108 | }
|
109 | return (!!currentSourceFile &&
|
110 | currentSourceFile !== valueDeclaration.getSourceFile());
|
111 | };
|
112 | const getNodeName = (node) => node.type === utils_1.AST_NODE_TYPES.Identifier ? node.name : null;
|
113 | const getMemberFullName = (node) => `${getNodeName(node.object)}.${getNodeName(node.property)}`;
|
114 | const BASE_MESSAGE = 'Avoid referencing unbound methods which may cause unintentional scoping of `this`.';
|
115 | exports.default = util.createRule({
|
116 | name: 'unbound-method',
|
117 | meta: {
|
118 | docs: {
|
119 | description: 'Enforces unbound methods are called with their expected scope',
|
120 | recommended: 'error',
|
121 | requiresTypeChecking: true,
|
122 | },
|
123 | messages: {
|
124 | unbound: BASE_MESSAGE,
|
125 | unboundWithoutThisAnnotation: BASE_MESSAGE +
|
126 | '\n' +
|
127 | 'If your function does not access `this`, you can annotate it with `this: void`, or consider using an arrow function instead.',
|
128 | },
|
129 | schema: [
|
130 | {
|
131 | type: 'object',
|
132 | properties: {
|
133 | ignoreStatic: {
|
134 | type: 'boolean',
|
135 | },
|
136 | },
|
137 | additionalProperties: false,
|
138 | },
|
139 | ],
|
140 | type: 'problem',
|
141 | },
|
142 | defaultOptions: [
|
143 | {
|
144 | ignoreStatic: false,
|
145 | },
|
146 | ],
|
147 | create(context, [{ ignoreStatic }]) {
|
148 | const parserServices = util.getParserServices(context);
|
149 | const checker = parserServices.program.getTypeChecker();
|
150 | const currentSourceFile = parserServices.program.getSourceFile(context.getFilename());
|
151 | function checkMethodAndReport(node, symbol) {
|
152 | if (!symbol) {
|
153 | return;
|
154 | }
|
155 | const { dangerous, firstParamIsThis } = checkMethod(symbol, ignoreStatic);
|
156 | if (dangerous) {
|
157 | context.report({
|
158 | messageId: firstParamIsThis === false
|
159 | ? 'unboundWithoutThisAnnotation'
|
160 | : 'unbound',
|
161 | node,
|
162 | });
|
163 | }
|
164 | }
|
165 | return {
|
166 | MemberExpression(node) {
|
167 | if (isSafeUse(node)) {
|
168 | return;
|
169 | }
|
170 | const objectSymbol = checker.getSymbolAtLocation(parserServices.esTreeNodeToTSNodeMap.get(node.object));
|
171 | if (objectSymbol &&
|
172 | nativelyBoundMembers.includes(getMemberFullName(node)) &&
|
173 | isNotImported(objectSymbol, currentSourceFile)) {
|
174 | return;
|
175 | }
|
176 | const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
177 | checkMethodAndReport(node, checker.getSymbolAtLocation(originalNode));
|
178 | },
|
179 | 'VariableDeclarator, AssignmentExpression'(node) {
|
180 | const [idNode, initNode] = node.type === utils_1.AST_NODE_TYPES.VariableDeclarator
|
181 | ? [node.id, node.init]
|
182 | : [node.left, node.right];
|
183 | if (initNode && idNode.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
184 | const tsNode = parserServices.esTreeNodeToTSNodeMap.get(initNode);
|
185 | const rightSymbol = checker.getSymbolAtLocation(tsNode);
|
186 | const initTypes = checker.getTypeAtLocation(tsNode);
|
187 | const notImported = rightSymbol && isNotImported(rightSymbol, currentSourceFile);
|
188 | idNode.properties.forEach(property => {
|
189 | if (property.type === utils_1.AST_NODE_TYPES.Property &&
|
190 | property.key.type === utils_1.AST_NODE_TYPES.Identifier) {
|
191 | if (notImported &&
|
192 | util.isIdentifier(initNode) &&
|
193 | nativelyBoundMembers.includes(`${initNode.name}.${property.key.name}`)) {
|
194 | return;
|
195 | }
|
196 | checkMethodAndReport(node, initTypes.getProperty(property.key.name));
|
197 | }
|
198 | });
|
199 | }
|
200 | },
|
201 | };
|
202 | },
|
203 | });
|
204 | function checkMethod(symbol, ignoreStatic) {
|
205 | var _a, _b;
|
206 | const { valueDeclaration } = symbol;
|
207 | if (!valueDeclaration) {
|
208 |
|
209 | return { dangerous: false };
|
210 | }
|
211 | switch (valueDeclaration.kind) {
|
212 | case ts.SyntaxKind.PropertyDeclaration:
|
213 | return {
|
214 | dangerous: ((_a = valueDeclaration.initializer) === null || _a === void 0 ? void 0 : _a.kind) ===
|
215 | ts.SyntaxKind.FunctionExpression,
|
216 | };
|
217 | case ts.SyntaxKind.MethodDeclaration:
|
218 | case ts.SyntaxKind.MethodSignature: {
|
219 | const decl = valueDeclaration;
|
220 | const firstParam = decl.parameters[0];
|
221 | const firstParamIsThis = (firstParam === null || firstParam === void 0 ? void 0 : firstParam.name.kind) === ts.SyntaxKind.Identifier &&
|
222 | (firstParam === null || firstParam === void 0 ? void 0 : firstParam.name.escapedText) === 'this';
|
223 | const thisArgIsVoid = firstParamIsThis &&
|
224 | ((_b = firstParam === null || firstParam === void 0 ? void 0 : firstParam.type) === null || _b === void 0 ? void 0 : _b.kind) === ts.SyntaxKind.VoidKeyword;
|
225 | return {
|
226 | dangerous: !thisArgIsVoid &&
|
227 | !(ignoreStatic &&
|
228 | tsutils.hasModifier(valueDeclaration.modifiers, ts.SyntaxKind.StaticKeyword)),
|
229 | firstParamIsThis,
|
230 | };
|
231 | }
|
232 | }
|
233 | return { dangerous: false };
|
234 | }
|
235 | function isSafeUse(node) {
|
236 | const parent = node.parent;
|
237 | switch (parent === null || parent === void 0 ? void 0 : parent.type) {
|
238 | case utils_1.AST_NODE_TYPES.IfStatement:
|
239 | case utils_1.AST_NODE_TYPES.ForStatement:
|
240 | case utils_1.AST_NODE_TYPES.MemberExpression:
|
241 | case utils_1.AST_NODE_TYPES.SwitchStatement:
|
242 | case utils_1.AST_NODE_TYPES.UpdateExpression:
|
243 | case utils_1.AST_NODE_TYPES.WhileStatement:
|
244 | return true;
|
245 | case utils_1.AST_NODE_TYPES.CallExpression:
|
246 | return parent.callee === node;
|
247 | case utils_1.AST_NODE_TYPES.ConditionalExpression:
|
248 | return parent.test === node;
|
249 | case utils_1.AST_NODE_TYPES.TaggedTemplateExpression:
|
250 | return parent.tag === node;
|
251 | case utils_1.AST_NODE_TYPES.UnaryExpression:
|
252 |
|
253 |
|
254 |
|
255 | return ['typeof', '!', 'void', 'delete'].includes(parent.operator);
|
256 | case utils_1.AST_NODE_TYPES.BinaryExpression:
|
257 | return ['instanceof', '==', '!=', '===', '!=='].includes(parent.operator);
|
258 | case utils_1.AST_NODE_TYPES.AssignmentExpression:
|
259 | return (parent.operator === '=' &&
|
260 | (node === parent.left ||
|
261 | (node.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
262 | node.object.type === utils_1.AST_NODE_TYPES.Super &&
|
263 | parent.left.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
264 | parent.left.object.type === utils_1.AST_NODE_TYPES.ThisExpression)));
|
265 | case utils_1.AST_NODE_TYPES.ChainExpression:
|
266 | case utils_1.AST_NODE_TYPES.TSNonNullExpression:
|
267 | case utils_1.AST_NODE_TYPES.TSAsExpression:
|
268 | case utils_1.AST_NODE_TYPES.TSTypeAssertion:
|
269 | return isSafeUse(parent);
|
270 | case utils_1.AST_NODE_TYPES.LogicalExpression:
|
271 | if (parent.operator === '&&' && parent.left === node) {
|
272 |
|
273 | return true;
|
274 | }
|
275 |
|
276 |
|
277 | return isSafeUse(parent);
|
278 | }
|
279 | return false;
|
280 | }
|
281 |
|
\ | No newline at end of file |