forked from ChromeDevTools/devtools-frontend
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathenforce-custom-event-names.js
130 lines (114 loc) · 4.46 KB
/
enforce-custom-event-names.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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// 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';
/**
* Match all lowercase letters from the start to the end.
*/
const VALID_EVENT_NAME_REGEX = /^([a-z]+)$/;
/**
* @type {import('eslint').Rule.RuleModule}
*/
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'check for event naming',
category: 'Possible Errors',
},
fixable: 'code',
messages: {
invalidEventName: 'Custom events must be named in all lower case with no punctuation.',
invalidEventNameReference: 'When referencing a custom event name, it must be accessed as ClassName.eventName.'
},
schema: [] // no options
},
create: function(context) {
let foundLocalEventClassDeclaration = false;
const classDeclarationsToLint = [];
function lintClassNode(node) {
const constructor =
node.body.body.find(node => node.type === 'MethodDefinition' && node.kind === 'constructor');
if (!constructor) {
return;
}
const superCall = constructor.value.body.body.find(bodyNode => {
return bodyNode.type === 'ExpressionStatement' && bodyNode.expression.type === 'CallExpression' &&
bodyNode.expression.callee.type === 'Super';
});
if (!superCall) {
return;
}
const firstArgToSuper = superCall.expression.arguments[0];
if (!firstArgToSuper) {
// This is invalid, but TypeScript will catch this for us so no need to
// error in ESLint land as well.
return;
}
if (firstArgToSuper.type === 'Literal') {
const firstArgLiteralValue = firstArgToSuper.value;
if (!firstArgLiteralValue.match(VALID_EVENT_NAME_REGEX)) {
context.report({node, messageId: 'invalidEventName'});
}
return;
}
if (firstArgToSuper.type !== 'MemberExpression') {
// This means it's a variable but not of the form ClassName.eventName, which we do not allow.
context.report({node, messageId: 'invalidEventNameReference'});
return;
}
// the name of the custom event class we're looking at
const eventClassName = node.id.name;
const objectName = firstArgToSuper.object.name;
const propertyName = firstArgToSuper.property.name;
if (objectName !== eventClassName || propertyName !== 'eventName') {
context.report({node, messageId: 'invalidEventNameReference'});
return;
}
// If the reference is right, let's find the value of the static eventName property and make sure it is valid.
const eventNameProperty = node.body.body.find(classBodyPart => {
return classBodyPart.type === 'PropertyDefinition' && classBodyPart.key.name === 'eventName';
});
// This should always exist because we checked for its existence
// previously, no error loudly as this is a bug in the lint rule.
if (!eventNameProperty) {
throw new Error(`Could not find static eventName property for ${eventClassName}.`);
}
// We don't let people use static eventName = SOME_VAR;
if (eventNameProperty.value.type !== 'Literal') {
context.report({node, messageId: 'invalidEventNameReference'});
return;
}
// Grab the value of static eventName and confirm it follows the
// required conventions.
const valueOfEventName = eventNameProperty.value.value;
if (!valueOfEventName.match(VALID_EVENT_NAME_REGEX)) {
context.report({node, messageId: 'invalidEventName'});
}
}
return {
ClassDeclaration(node) {
// If we find a local class defined called Event, we do not apply this
// check, as we have some instances where a local Event class is used
// which is not the builtin Event class that represents DOM emitted
// events.
if (node.id.name === 'Event') {
foundLocalEventClassDeclaration = true;
return;
}
if (!node.superClass || node.superClass.name !== 'Event') {
return;
}
classDeclarationsToLint.push(node);
},
'Program:exit'() {
if (foundLocalEventClassDeclaration) {
return;
}
classDeclarationsToLint.forEach(node => {
lintClassNode(node);
});
},
};
}
};