forked from ChromeDevTools/devtools-frontend
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathenforce-bound-render-for-schedule-render.js
113 lines (97 loc) · 3.79 KB
/
enforce-bound-render-for-schedule-render.js
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
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
function goToClassDeclaration(node) {
if (!node) {
return null;
}
if (node.type === 'ClassDeclaration') {
return node;
}
return goToClassDeclaration(node.parent);
}
function isMemberExpressionOnThis(memberExpression) {
if (!memberExpression) {
return false;
}
if (memberExpression.object.type === 'ThisExpression') {
// Take into `a.this.bind()` case into account
// `this` must be the last object in the `MemberExpression` chain
return !memberExpression.object.object;
}
return isMemberExpressionOnThis(memberExpression.object);
}
// Whether the right hand side of property definition is `this.xxx.yyy.bind(this);`
function isPropertyDefinitionViaBindCallToThis(propertyDefinition) {
if (propertyDefinition.value.type !== 'CallExpression' ||
propertyDefinition.value.callee.type !== 'MemberExpression') {
return false;
}
const isCalleeObjectThis = isMemberExpressionOnThis(propertyDefinition.value.callee);
// Whether the CallExpression is on a property of `this` (this.xxx.yyy.bind)
if (!isCalleeObjectThis) {
return false;
}
const isItBindCall = propertyDefinition.value.callee.property.name === 'bind';
// Whether the CallExpression is a `bind` call on a property of `this`
if (!isItBindCall) {
return false;
}
const callArgument = propertyDefinition.value.arguments[0];
// Call argument to `bind` is not `this`
if (!callArgument || callArgument.type !== 'ThisExpression') {
return false;
}
return true;
}
// Whether the property definition is arrow function like `#render = () => {}`
function isPropertyDefinitionViaArrowFunction(propertyDefinition) {
return propertyDefinition.value.type === 'ArrowFunctionExpression';
}
/**
* @type {import('eslint').Rule.RuleModule}
*/
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Enforce render method to be bound while calling scheduleRender',
category: 'Possible Errors',
},
fixable: 'code',
schema: [] // no options
},
create: function(context) {
return {
CallExpression(node) {
// Calls in the form of `ScheduledRender.scheduleRender`
const isScheduleRenderCall = node.callee.type === 'MemberExpression' &&
node.callee.object?.property?.name === 'ScheduledRender' && node.callee.property?.name === 'scheduleRender';
if (!isScheduleRenderCall) {
return;
}
const callbackArgument = node.arguments[1];
// Whether the second argument points to a property of `this`
// like `ScheduledRender.scheduleRender(<any>, this.<any>)
if (callbackArgument.type !== 'MemberExpression' || callbackArgument.object.type !== 'ThisExpression') {
return;
}
const containingClassForTheCall = goToClassDeclaration(node);
// Only care about the calls in custom components
if (!containingClassForTheCall.superClass || containingClassForTheCall.superClass.name !== 'HTMLElement') {
return;
}
const calledMethod = callbackArgument.property;
// Check whether the called method is bound (it should be 'PropertyDefinition')
const propertyDefinition = containingClassForTheCall.body.body.find(
bodyNode => bodyNode.type === 'PropertyDefinition' && bodyNode.key.name === calledMethod.name);
if (!propertyDefinition ||
(!isPropertyDefinitionViaArrowFunction(propertyDefinition) &&
!isPropertyDefinitionViaBindCallToThis(propertyDefinition))) {
context.report({node, message: 'Bind `render` method of `scheduleRender` to `this` in components'});
}
}
};
}
};