@agilebot/eslint-plugin 0.8.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +4 -3
- package/dist/index.mjs +2644 -0
- package/package.json +12 -4
package/dist/index.mjs
ADDED
@@ -0,0 +1,2644 @@
|
|
1
|
+
/**
|
2
|
+
* @license @agilebot/eslint-plugin v0.8.2
|
3
|
+
*
|
4
|
+
* Copyright (c) Agilebot, Inc. and its affiliates.
|
5
|
+
*
|
6
|
+
* This source code is licensed under the MIT license found in the
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
8
|
+
*/
|
9
|
+
import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
|
10
|
+
import * as fs from 'node:fs';
|
11
|
+
import * as path from 'node:path';
|
12
|
+
import path__default from 'node:path';
|
13
|
+
import { tsImport, isPascalCase, isCamelCase } from '@agilebot/eslint-utils';
|
14
|
+
import * as Components from 'eslint-plugin-react/lib/util/Components.js';
|
15
|
+
|
16
|
+
const docBaseUrl = 'https://github.com/sh-agilebot/frontend-toolkit/blob/master/packages/eslint-plugin/src/rules/';
|
17
|
+
const hasDocs = typeof ["enforce-mui-icon-alias","intl-id-missing","intl-id-prefix","intl-no-default","react-better-exhaustive-deps"] !== 'undefined' ?
|
18
|
+
["enforce-mui-icon-alias","intl-id-missing","intl-id-prefix","intl-no-default","react-better-exhaustive-deps"] : [];
|
19
|
+
const createRule = ESLintUtils.RuleCreator(name => hasDocs.includes(name) ? "".concat(docBaseUrl).concat(name, ".md") : "".concat(docBaseUrl).concat(name, ".test.js"));
|
20
|
+
|
21
|
+
const RULE_NAME$h = 'enforce-mui-icon-alias';
|
22
|
+
var enforceMuiIconAlias = createRule({
|
23
|
+
name: RULE_NAME$h,
|
24
|
+
meta: {
|
25
|
+
type: 'problem',
|
26
|
+
docs: {
|
27
|
+
description: 'Enforce alias for @mui/icons-material imports',
|
28
|
+
recommended: 'recommended'
|
29
|
+
},
|
30
|
+
fixable: 'code',
|
31
|
+
schema: [],
|
32
|
+
messages: {
|
33
|
+
iconAlias: 'Import for {{ name }} should be aliased.'
|
34
|
+
}
|
35
|
+
},
|
36
|
+
defaultOptions: [],
|
37
|
+
create(context) {
|
38
|
+
return {
|
39
|
+
ImportDeclaration(node) {
|
40
|
+
if (node.source.value !== '@mui/icons-material' && node.source.value !== 'mdi-material-ui') {
|
41
|
+
return;
|
42
|
+
}
|
43
|
+
for (const specifier of node.specifiers) {
|
44
|
+
if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) {
|
45
|
+
return;
|
46
|
+
}
|
47
|
+
if (specifier.imported.name === specifier.local.name || !specifier.local.name.endsWith('Icon')) {
|
48
|
+
context.report({
|
49
|
+
node,
|
50
|
+
messageId: 'iconAlias',
|
51
|
+
data: {
|
52
|
+
name: node.source.value
|
53
|
+
},
|
54
|
+
fix: fixer => fixer.replaceText(specifier, "".concat(specifier.imported.name, " as ").concat(specifier.imported.name, "Icon"))
|
55
|
+
});
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
};
|
60
|
+
}
|
61
|
+
});
|
62
|
+
|
63
|
+
function getSetting(context, name) {
|
64
|
+
return context.settings["agilebot/".concat(name)];
|
65
|
+
}
|
66
|
+
|
67
|
+
const RULE_NAME$g = 'import-monorepo';
|
68
|
+
var importMonorepo = createRule({
|
69
|
+
name: RULE_NAME$g,
|
70
|
+
meta: {
|
71
|
+
type: 'problem',
|
72
|
+
docs: {
|
73
|
+
description: 'Enforce import styles for monorepo',
|
74
|
+
recommended: 'recommended'
|
75
|
+
},
|
76
|
+
fixable: 'code',
|
77
|
+
schema: [],
|
78
|
+
messages: {
|
79
|
+
monorepoImport: 'Import for {{ module }} should not contains src folder.'
|
80
|
+
}
|
81
|
+
},
|
82
|
+
defaultOptions: [],
|
83
|
+
create(context) {
|
84
|
+
return {
|
85
|
+
ImportDeclaration(node) {
|
86
|
+
let prefix = getSetting(context, 'monorepo-scope');
|
87
|
+
if (!prefix) {
|
88
|
+
return;
|
89
|
+
}
|
90
|
+
prefix = "".concat(prefix, "/");
|
91
|
+
if (typeof node.source.value !== 'string') {
|
92
|
+
return;
|
93
|
+
}
|
94
|
+
if (!node.source.value.startsWith(prefix)) {
|
95
|
+
return;
|
96
|
+
}
|
97
|
+
const values = node.source.value.split('/');
|
98
|
+
if (values[2] === 'src') {
|
99
|
+
context.report({
|
100
|
+
node,
|
101
|
+
messageId: 'monorepoImport',
|
102
|
+
data: {
|
103
|
+
module: node.source.value
|
104
|
+
},
|
105
|
+
fix: fixer => {
|
106
|
+
const correctedPath = values.filter((_, index) => index !== 2).join('/');
|
107
|
+
return fixer.replaceText(node.source, "'".concat(correctedPath, "'"));
|
108
|
+
}
|
109
|
+
});
|
110
|
+
}
|
111
|
+
}
|
112
|
+
};
|
113
|
+
}
|
114
|
+
});
|
115
|
+
|
116
|
+
function findFormatMessageAttrNode(node, attrName) {
|
117
|
+
if (node.type === AST_NODE_TYPES.CallExpression && (node.callee.name === 'formatMessage' || node.callee.name === '$t') && node.arguments.length > 0 && node.arguments[0].properties) {
|
118
|
+
return node.arguments[0].properties.find(a => a.key && a.key.name === attrName);
|
119
|
+
}
|
120
|
+
if (node.type === AST_NODE_TYPES.CallExpression && node.callee.type === AST_NODE_TYPES.MemberExpression && (node.callee.object.name === 'intl' || node.callee.object.name && node.callee.object.name.endsWith('Intl')) && (node.callee.property.name === 'formatMessage' || node.callee.property.name === '$t')) {
|
121
|
+
return node.arguments[0].properties.find(a => a.key && a.key.name === attrName);
|
122
|
+
}
|
123
|
+
}
|
124
|
+
function findFormattedMessageAttrNode(node, attrName) {
|
125
|
+
if (node.type === AST_NODE_TYPES.JSXIdentifier && node.name === 'FormattedMessage' && node.parent && node.parent.type === AST_NODE_TYPES.JSXOpeningElement) {
|
126
|
+
return node.parent.attributes.find(a => a.name && a.name.name === attrName);
|
127
|
+
}
|
128
|
+
}
|
129
|
+
function findAttrNodeInDefineMessages(node, attrName) {
|
130
|
+
if (node.type === 'Property' && node.key.name === attrName && node.parent && node.parent.parent && node.parent.parent.parent && node.parent.parent.parent.parent && node.parent.parent.parent.parent.type === AST_NODE_TYPES.CallExpression && node.parent.parent.parent.parent.callee.name === 'defineMessages') {
|
131
|
+
return node;
|
132
|
+
}
|
133
|
+
}
|
134
|
+
function findAttrNodeInDefineMessage(node, attrName) {
|
135
|
+
if (node.type === 'Property' && node.key.name === attrName && node.parent && node.parent.parent && node.parent.parent.type === AST_NODE_TYPES.CallExpression && node.parent.parent.callee.name === 'defineMessage') {
|
136
|
+
return node;
|
137
|
+
}
|
138
|
+
}
|
139
|
+
function sortedTemplateElements(node) {
|
140
|
+
return [...node.quasis, ...node.expressions].sort((a, b) => a.range[0] - b.range[0]);
|
141
|
+
}
|
142
|
+
function templateLiteralDisplayStr(node) {
|
143
|
+
return sortedTemplateElements(node).map(e => !e.value ? '*' : e.value.raw).join('');
|
144
|
+
}
|
145
|
+
|
146
|
+
const localeFilesKeys = {};
|
147
|
+
function getIntlIds(context) {
|
148
|
+
const projectRoot = getSetting(context, 'project-root');
|
149
|
+
const localeFiles = getSetting(context, 'locale-files');
|
150
|
+
if (!localeFiles) {
|
151
|
+
throw new Error('localeFiles not in settings');
|
152
|
+
}
|
153
|
+
const results = [];
|
154
|
+
localeFiles.forEach(f => {
|
155
|
+
const fullPath = projectRoot ? path.join(projectRoot, f) : f;
|
156
|
+
const mtime = fs.lstatSync(fullPath).mtime.getTime();
|
157
|
+
if (!localeFilesKeys[fullPath] || mtime !== localeFilesKeys[fullPath].mtime) {
|
158
|
+
let json;
|
159
|
+
if (fullPath.endsWith('.json')) {
|
160
|
+
json = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
|
161
|
+
} else if (fullPath.endsWith('.ts')) {
|
162
|
+
json = tsImport(fullPath);
|
163
|
+
if (typeof json === 'object' && json["default"]) {
|
164
|
+
json = json["default"];
|
165
|
+
}
|
166
|
+
} else if (fullPath.endsWith('.js')) {
|
167
|
+
json = require(fullPath);
|
168
|
+
if (typeof json === 'object' && json["default"]) {
|
169
|
+
json = json["default"];
|
170
|
+
}
|
171
|
+
} else {
|
172
|
+
throw new Error('unsupported file extension');
|
173
|
+
}
|
174
|
+
localeFilesKeys[fullPath] = {
|
175
|
+
keys: Object.keys(json),
|
176
|
+
mtime: mtime
|
177
|
+
};
|
178
|
+
}
|
179
|
+
results.push(...localeFilesKeys[fullPath].keys);
|
180
|
+
});
|
181
|
+
return results;
|
182
|
+
}
|
183
|
+
|
184
|
+
const RULE_NAME$f = 'intl-id-missing';
|
185
|
+
var intlIdMissing = createRule({
|
186
|
+
name: RULE_NAME$f,
|
187
|
+
meta: {
|
188
|
+
type: 'problem',
|
189
|
+
docs: {
|
190
|
+
description: 'Validates intl message ids are in locale file'
|
191
|
+
},
|
192
|
+
schema: [],
|
193
|
+
messages: {
|
194
|
+
missingId: 'Missing id: {{value}}',
|
195
|
+
missingIdPattern: 'Missing id pattern: {{value}}',
|
196
|
+
disallowInvoke: 'Do not invoke intl by {{value}}'
|
197
|
+
}
|
198
|
+
},
|
199
|
+
defaultOptions: [],
|
200
|
+
create: function (context) {
|
201
|
+
const translatedIds = getIntlIds(context);
|
202
|
+
const translatedIdSet = new Set(translatedIds);
|
203
|
+
function isLiteralTranslated(id) {
|
204
|
+
return translatedIdSet.has(id);
|
205
|
+
}
|
206
|
+
function isTemplateTranslated(re) {
|
207
|
+
return translatedIds.some(k => re.test(k));
|
208
|
+
}
|
209
|
+
function processLiteral(node) {
|
210
|
+
if (!isLiteralTranslated(node.value)) {
|
211
|
+
context.report({
|
212
|
+
node: node,
|
213
|
+
messageId: 'missingId',
|
214
|
+
data: {
|
215
|
+
value: node.value
|
216
|
+
}
|
217
|
+
});
|
218
|
+
}
|
219
|
+
}
|
220
|
+
function processTemplateLiteral(node) {
|
221
|
+
const exStr = sortedTemplateElements(node).map(e => !e.value ? '.*' : e.value.raw).join('');
|
222
|
+
const re = new RegExp(exStr);
|
223
|
+
if (!isTemplateTranslated(re)) {
|
224
|
+
context.report({
|
225
|
+
node: node,
|
226
|
+
messageId: 'missingIdPattern',
|
227
|
+
data: {
|
228
|
+
value: templateLiteralDisplayStr(node)
|
229
|
+
}
|
230
|
+
});
|
231
|
+
}
|
232
|
+
}
|
233
|
+
function processAttrNode(node) {
|
234
|
+
if (node.value.type === AST_NODE_TYPES.Literal) {
|
235
|
+
return processLiteral(node.value);
|
236
|
+
}
|
237
|
+
if (node.value.type === AST_NODE_TYPES.JSXExpressionContainer && node.value.expression.type === AST_NODE_TYPES.TemplateLiteral) {
|
238
|
+
return processTemplateLiteral(node.value.expression);
|
239
|
+
}
|
240
|
+
if (node.value.type === AST_NODE_TYPES.TemplateLiteral) {
|
241
|
+
return processTemplateLiteral(node.value);
|
242
|
+
}
|
243
|
+
context.report({
|
244
|
+
node: node,
|
245
|
+
messageId: 'disallowInvoke',
|
246
|
+
data: {
|
247
|
+
value: node.value.type
|
248
|
+
}
|
249
|
+
});
|
250
|
+
}
|
251
|
+
return {
|
252
|
+
JSXIdentifier: function (node) {
|
253
|
+
const attrNode = findFormattedMessageAttrNode(node, 'id');
|
254
|
+
if (attrNode) {
|
255
|
+
return processAttrNode(attrNode);
|
256
|
+
}
|
257
|
+
},
|
258
|
+
CallExpression: function (node) {
|
259
|
+
const attrNode = findFormatMessageAttrNode(node, 'id');
|
260
|
+
if (attrNode) {
|
261
|
+
return processAttrNode(attrNode);
|
262
|
+
}
|
263
|
+
},
|
264
|
+
Property: function (node) {
|
265
|
+
const attrNode = findAttrNodeInDefineMessages(node, 'id') || findAttrNodeInDefineMessage(node, 'id');
|
266
|
+
if (attrNode) {
|
267
|
+
return processAttrNode(attrNode);
|
268
|
+
}
|
269
|
+
}
|
270
|
+
};
|
271
|
+
}
|
272
|
+
});
|
273
|
+
|
274
|
+
const RULE_NAME$e = 'intl-id-naming';
|
275
|
+
var intlIdNaming = createRule({
|
276
|
+
name: RULE_NAME$e,
|
277
|
+
meta: {
|
278
|
+
type: 'problem',
|
279
|
+
docs: {
|
280
|
+
description: 'Validates intl message ids naming convention'
|
281
|
+
},
|
282
|
+
schema: [{
|
283
|
+
type: 'object',
|
284
|
+
properties: {
|
285
|
+
format: {
|
286
|
+
type: 'string',
|
287
|
+
"enum": ['camelCase', 'PascalCase']
|
288
|
+
}
|
289
|
+
}
|
290
|
+
}],
|
291
|
+
messages: {
|
292
|
+
invalidIdNaming: "Invalid id naming, expected {{format}}"
|
293
|
+
}
|
294
|
+
},
|
295
|
+
defaultOptions: [],
|
296
|
+
create: function (context) {
|
297
|
+
if (!context.options[0]) {
|
298
|
+
throw new Error('Missing options');
|
299
|
+
}
|
300
|
+
const format = context.options[0].format;
|
301
|
+
function report(node, value) {
|
302
|
+
const values = value.split('.');
|
303
|
+
let isPass;
|
304
|
+
for (const v of values) {
|
305
|
+
switch (format) {
|
306
|
+
case 'camelCase':
|
307
|
+
if (!isCamelCase(v)) {
|
308
|
+
isPass = false;
|
309
|
+
}
|
310
|
+
break;
|
311
|
+
case 'PascalCase':
|
312
|
+
if (!isPascalCase(v)) {
|
313
|
+
isPass = false;
|
314
|
+
}
|
315
|
+
break;
|
316
|
+
}
|
317
|
+
}
|
318
|
+
if (isPass === false) {
|
319
|
+
context.report({
|
320
|
+
node: node,
|
321
|
+
messageId: 'invalidIdNaming',
|
322
|
+
data: {
|
323
|
+
format
|
324
|
+
}
|
325
|
+
});
|
326
|
+
}
|
327
|
+
}
|
328
|
+
function processLiteral(node) {
|
329
|
+
report(node, node.value);
|
330
|
+
}
|
331
|
+
function processTemplateLiteral(node) {
|
332
|
+
const displayStr = templateLiteralDisplayStr(node);
|
333
|
+
report(node, displayStr);
|
334
|
+
}
|
335
|
+
function processAttrNode(node) {
|
336
|
+
if (node.value.type === AST_NODE_TYPES.Literal) {
|
337
|
+
return processLiteral(node.value);
|
338
|
+
}
|
339
|
+
if (node.value.type === AST_NODE_TYPES.JSXExpressionContainer && node.value.expression.type === AST_NODE_TYPES.TemplateLiteral) {
|
340
|
+
return processTemplateLiteral(node.value.expression);
|
341
|
+
}
|
342
|
+
if (node.value.type === AST_NODE_TYPES.TemplateLiteral) {
|
343
|
+
return processTemplateLiteral(node.value);
|
344
|
+
}
|
345
|
+
}
|
346
|
+
return {
|
347
|
+
JSXIdentifier: function (node) {
|
348
|
+
const attrNode = findFormattedMessageAttrNode(node, 'id');
|
349
|
+
if (attrNode) {
|
350
|
+
return processAttrNode(attrNode);
|
351
|
+
}
|
352
|
+
},
|
353
|
+
CallExpression: function (node) {
|
354
|
+
const attrNode = findFormatMessageAttrNode(node, 'id');
|
355
|
+
if (attrNode) {
|
356
|
+
return processAttrNode(attrNode);
|
357
|
+
}
|
358
|
+
},
|
359
|
+
Property: function (node) {
|
360
|
+
const attrNode = findAttrNodeInDefineMessages(node, 'id') || findAttrNodeInDefineMessage(node, 'id');
|
361
|
+
if (attrNode) {
|
362
|
+
return processAttrNode(attrNode);
|
363
|
+
}
|
364
|
+
}
|
365
|
+
};
|
366
|
+
}
|
367
|
+
});
|
368
|
+
|
369
|
+
const RULE_NAME$d = 'intl-id-prefix';
|
370
|
+
var intlIdPrefix = createRule({
|
371
|
+
name: RULE_NAME$d,
|
372
|
+
meta: {
|
373
|
+
type: 'problem',
|
374
|
+
docs: {
|
375
|
+
description: 'Validates intl message ids has correct prefixes'
|
376
|
+
},
|
377
|
+
schema: [{
|
378
|
+
type: 'array',
|
379
|
+
items: {
|
380
|
+
type: 'string'
|
381
|
+
}
|
382
|
+
}],
|
383
|
+
messages: {
|
384
|
+
invalidIdPrefix: 'Invalid id prefix: {{value}}'
|
385
|
+
}
|
386
|
+
},
|
387
|
+
defaultOptions: [],
|
388
|
+
create: function (context) {
|
389
|
+
if (context.options[0].length === 0) {
|
390
|
+
throw new Error('Prefixes are required in settings');
|
391
|
+
}
|
392
|
+
const hasPrefix = value => context.options[0].some(p => value.startsWith(p));
|
393
|
+
function report(node, value) {
|
394
|
+
if (!hasPrefix(value)) {
|
395
|
+
context.report({
|
396
|
+
node: node,
|
397
|
+
messageId: 'invalidIdPrefix',
|
398
|
+
data: {
|
399
|
+
value
|
400
|
+
}
|
401
|
+
});
|
402
|
+
}
|
403
|
+
}
|
404
|
+
function processLiteral(node) {
|
405
|
+
report(node, node.value);
|
406
|
+
}
|
407
|
+
function processTemplateLiteral(node) {
|
408
|
+
const displayStr = templateLiteralDisplayStr(node);
|
409
|
+
report(node, displayStr);
|
410
|
+
}
|
411
|
+
function processAttrNode(node) {
|
412
|
+
if (node.value.type === AST_NODE_TYPES.Literal) {
|
413
|
+
return processLiteral(node.value);
|
414
|
+
}
|
415
|
+
if (node.value.type === AST_NODE_TYPES.JSXExpressionContainer && node.value.expression.type === AST_NODE_TYPES.TemplateLiteral) {
|
416
|
+
return processTemplateLiteral(node.value.expression);
|
417
|
+
}
|
418
|
+
if (node.value.type === AST_NODE_TYPES.TemplateLiteral) {
|
419
|
+
return processTemplateLiteral(node.value);
|
420
|
+
}
|
421
|
+
}
|
422
|
+
return {
|
423
|
+
JSXIdentifier: function (node) {
|
424
|
+
const attrNode = findFormattedMessageAttrNode(node, 'id');
|
425
|
+
if (attrNode) {
|
426
|
+
return processAttrNode(attrNode);
|
427
|
+
}
|
428
|
+
},
|
429
|
+
CallExpression: function (node) {
|
430
|
+
const attrNode = findFormatMessageAttrNode(node, 'id');
|
431
|
+
if (attrNode) {
|
432
|
+
return processAttrNode(attrNode);
|
433
|
+
}
|
434
|
+
},
|
435
|
+
Property: function (node) {
|
436
|
+
const attrNode = findAttrNodeInDefineMessages(node, 'id') || findAttrNodeInDefineMessage(node, 'id');
|
437
|
+
if (attrNode) {
|
438
|
+
return processAttrNode(attrNode);
|
439
|
+
}
|
440
|
+
}
|
441
|
+
};
|
442
|
+
}
|
443
|
+
});
|
444
|
+
|
445
|
+
const RULE_NAME$c = 'intl-id-unused';
|
446
|
+
const usedIds = new Map();
|
447
|
+
var intlIdUnused = createRule({
|
448
|
+
name: RULE_NAME$c,
|
449
|
+
meta: {
|
450
|
+
type: 'problem',
|
451
|
+
docs: {
|
452
|
+
description: 'Finds unused intl message ids in locale file'
|
453
|
+
},
|
454
|
+
schema: [],
|
455
|
+
messages: {}
|
456
|
+
},
|
457
|
+
defaultOptions: [],
|
458
|
+
create: function (context) {
|
459
|
+
const projectRoot = getSetting(context, 'project-root');
|
460
|
+
if (!projectRoot) {
|
461
|
+
throw new Error('projectRoot must be set in this rule');
|
462
|
+
}
|
463
|
+
if (!usedIds.has(projectRoot)) {
|
464
|
+
usedIds.set(projectRoot, new Set());
|
465
|
+
}
|
466
|
+
const usedIdSet = usedIds.get(projectRoot);
|
467
|
+
const translatedIds = getIntlIds(context);
|
468
|
+
const translatedIdSet = new Set(translatedIds);
|
469
|
+
function isLiteralTranslated(id) {
|
470
|
+
return translatedIdSet.has(id);
|
471
|
+
}
|
472
|
+
function isTemplateTranslated(re) {
|
473
|
+
return translatedIds.some(k => re.test(k));
|
474
|
+
}
|
475
|
+
function processLiteral(node) {
|
476
|
+
if (isLiteralTranslated(node.value)) {
|
477
|
+
usedIdSet.add(node.value);
|
478
|
+
}
|
479
|
+
}
|
480
|
+
function processTemplateLiteral(node) {
|
481
|
+
const exStr = sortedTemplateElements(node).map(e => !e.value ? '.*' : e.value.raw).join('');
|
482
|
+
const re = new RegExp(exStr);
|
483
|
+
if (isTemplateTranslated(re)) ;
|
484
|
+
}
|
485
|
+
function processAttrNode(node) {
|
486
|
+
if (node.value.type === AST_NODE_TYPES.Literal) {
|
487
|
+
return processLiteral(node.value);
|
488
|
+
}
|
489
|
+
if (node.value.type === AST_NODE_TYPES.JSXExpressionContainer && node.value.expression.type === AST_NODE_TYPES.TemplateLiteral) {
|
490
|
+
return processTemplateLiteral(node.value.expression);
|
491
|
+
}
|
492
|
+
if (node.value.type === AST_NODE_TYPES.TemplateLiteral) {
|
493
|
+
return processTemplateLiteral(node.value);
|
494
|
+
}
|
495
|
+
}
|
496
|
+
return {
|
497
|
+
JSXIdentifier: function (node) {
|
498
|
+
const attrNode = findFormattedMessageAttrNode(node, 'id');
|
499
|
+
if (attrNode) {
|
500
|
+
return processAttrNode(attrNode);
|
501
|
+
}
|
502
|
+
},
|
503
|
+
CallExpression: function (node) {
|
504
|
+
const attrNode = findFormatMessageAttrNode(node, 'id');
|
505
|
+
if (attrNode) {
|
506
|
+
return processAttrNode(attrNode);
|
507
|
+
}
|
508
|
+
},
|
509
|
+
Property: function (node) {
|
510
|
+
const attrNode = findAttrNodeInDefineMessages(node, 'id') || findAttrNodeInDefineMessage(node, 'id');
|
511
|
+
if (attrNode) {
|
512
|
+
return processAttrNode(attrNode);
|
513
|
+
}
|
514
|
+
},
|
515
|
+
'Program:exit': function () {
|
516
|
+
const unusedIds = [...translatedIdSet].filter(id => !usedIdSet.has(id));
|
517
|
+
const jsonPath = path.join(projectRoot, 'intl-unused.json');
|
518
|
+
fs.writeFileSync(jsonPath, JSON.stringify(unusedIds, null, 2));
|
519
|
+
}
|
520
|
+
};
|
521
|
+
}
|
522
|
+
});
|
523
|
+
|
524
|
+
const RULE_NAME$b = 'intl-no-default';
|
525
|
+
var intlNoDefault = createRule({
|
526
|
+
name: RULE_NAME$b,
|
527
|
+
meta: {
|
528
|
+
type: 'problem',
|
529
|
+
docs: {
|
530
|
+
description: 'Validates defaultMessage is not used with react-intl'
|
531
|
+
},
|
532
|
+
schema: [],
|
533
|
+
messages: {
|
534
|
+
noDefaultMessage: 'Do not use defaultMessage'
|
535
|
+
}
|
536
|
+
},
|
537
|
+
defaultOptions: [],
|
538
|
+
create: function (context) {
|
539
|
+
function processAttrNode(node) {
|
540
|
+
context.report({
|
541
|
+
node: node,
|
542
|
+
messageId: 'noDefaultMessage'
|
543
|
+
});
|
544
|
+
}
|
545
|
+
return {
|
546
|
+
JSXIdentifier: function (node) {
|
547
|
+
const attrNode = findFormattedMessageAttrNode(node, 'defaultMessage');
|
548
|
+
if (attrNode) {
|
549
|
+
return processAttrNode(attrNode);
|
550
|
+
}
|
551
|
+
},
|
552
|
+
CallExpression: function (node) {
|
553
|
+
const attrNode = findFormatMessageAttrNode(node, 'defaultMessage');
|
554
|
+
if (attrNode) {
|
555
|
+
return processAttrNode(attrNode);
|
556
|
+
}
|
557
|
+
},
|
558
|
+
Property: function (node) {
|
559
|
+
const attrNode = findAttrNodeInDefineMessages(node, 'defaultMessage') || findAttrNodeInDefineMessage(node, 'defaultMessage');
|
560
|
+
if (attrNode) {
|
561
|
+
return processAttrNode(attrNode);
|
562
|
+
}
|
563
|
+
}
|
564
|
+
};
|
565
|
+
}
|
566
|
+
});
|
567
|
+
|
568
|
+
const RULE_NAME$a = 'no-async-array-methods';
|
569
|
+
var noAsyncArrayMethods = createRule({
|
570
|
+
name: RULE_NAME$a,
|
571
|
+
meta: {
|
572
|
+
type: 'problem',
|
573
|
+
docs: {
|
574
|
+
description: 'No async callback for Array methods forEach, map, filter, reduce, some, every, etc.',
|
575
|
+
recommended: 'recommended'
|
576
|
+
},
|
577
|
+
schema: [],
|
578
|
+
messages: {
|
579
|
+
noAsyncArrayMethods: "No async function in method '{{ methodName }}'"
|
580
|
+
}
|
581
|
+
},
|
582
|
+
defaultOptions: [],
|
583
|
+
create: function (context) {
|
584
|
+
return {
|
585
|
+
ExpressionStatement: function (node) {
|
586
|
+
const notAllowedArrayMethods = ['forEach', 'filter', 'some', 'every', 'map', 'reduce', 'reduceRight', 'flatMap', 'find', 'findIndex', 'findLast', 'findLastIndex'];
|
587
|
+
const {
|
588
|
+
callee
|
589
|
+
} = node.expression;
|
590
|
+
if (!callee || !callee.property || !callee.property.name) {
|
591
|
+
return;
|
592
|
+
}
|
593
|
+
if (notAllowedArrayMethods.includes(callee.property.name)) {
|
594
|
+
const functionArguments = node.expression.arguments.find(n => {
|
595
|
+
return [AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression].includes(n.type);
|
596
|
+
});
|
597
|
+
if (functionArguments && functionArguments.async) {
|
598
|
+
context.report({
|
599
|
+
node,
|
600
|
+
messageId: 'noAsyncArrayMethods',
|
601
|
+
data: {
|
602
|
+
methodName: callee.property.name
|
603
|
+
}
|
604
|
+
});
|
605
|
+
}
|
606
|
+
}
|
607
|
+
}
|
608
|
+
};
|
609
|
+
}
|
610
|
+
});
|
611
|
+
|
612
|
+
const RULE_NAME$9 = 'no-extends-error';
|
613
|
+
var noExtendsError = createRule({
|
614
|
+
name: RULE_NAME$9,
|
615
|
+
meta: {
|
616
|
+
type: 'problem',
|
617
|
+
docs: {
|
618
|
+
description: 'Disallow extending the Error class',
|
619
|
+
recommended: 'recommended'
|
620
|
+
},
|
621
|
+
schema: [],
|
622
|
+
messages: {
|
623
|
+
noExtendsError: "Extending the 'Error' class is prohibited."
|
624
|
+
}
|
625
|
+
},
|
626
|
+
defaultOptions: [],
|
627
|
+
create(context) {
|
628
|
+
return {
|
629
|
+
ClassDeclaration(node) {
|
630
|
+
if (node.superClass && node.superClass.type === AST_NODE_TYPES.Identifier && node.superClass.name === 'Error') {
|
631
|
+
context.report({
|
632
|
+
node: node.superClass,
|
633
|
+
messageId: 'noExtendsError'
|
634
|
+
});
|
635
|
+
}
|
636
|
+
}
|
637
|
+
};
|
638
|
+
}
|
639
|
+
});
|
640
|
+
|
641
|
+
const RULE_NAME$8 = 'no-import-css';
|
642
|
+
var noImportCss = createRule({
|
643
|
+
name: RULE_NAME$8,
|
644
|
+
meta: {
|
645
|
+
type: 'problem',
|
646
|
+
docs: {
|
647
|
+
description: 'Prevent importing CSS',
|
648
|
+
recommended: 'recommended'
|
649
|
+
},
|
650
|
+
schema: [],
|
651
|
+
messages: {
|
652
|
+
noImportCSS: 'Do not import CSS files. Use CSS-in-JS instead.'
|
653
|
+
}
|
654
|
+
},
|
655
|
+
defaultOptions: [],
|
656
|
+
create(context) {
|
657
|
+
return {
|
658
|
+
ImportDeclaration(node) {
|
659
|
+
const ext = path__default.extname(node.source.value);
|
660
|
+
if (ext.startsWith('.css') || ext.startsWith('.scss') || ext.startsWith('.sass') || ext.startsWith('.less') || ext.startsWith('.styl')) {
|
661
|
+
context.report({
|
662
|
+
node,
|
663
|
+
messageId: 'noImportCSS',
|
664
|
+
data: {
|
665
|
+
path: node.source.value
|
666
|
+
}
|
667
|
+
});
|
668
|
+
}
|
669
|
+
}
|
670
|
+
};
|
671
|
+
}
|
672
|
+
});
|
673
|
+
|
674
|
+
const RULE_NAME$7 = 'no-then-catch-finally';
|
675
|
+
var noThenCatchFinally = createRule({
|
676
|
+
name: RULE_NAME$7,
|
677
|
+
meta: {
|
678
|
+
type: 'suggestion',
|
679
|
+
docs: {
|
680
|
+
description: 'Disallow the use of then()/catch()/finally() when invoking specific functions.'
|
681
|
+
},
|
682
|
+
schema: [{
|
683
|
+
type: 'object',
|
684
|
+
properties: {
|
685
|
+
restrictedFunctions: {
|
686
|
+
type: 'array',
|
687
|
+
uniqueItems: true,
|
688
|
+
items: {
|
689
|
+
type: 'string'
|
690
|
+
}
|
691
|
+
}
|
692
|
+
}
|
693
|
+
}],
|
694
|
+
messages: {
|
695
|
+
forbiddenThenCatchFinally: "then()/catch()/finally() is forbidden when invoke {{ name }}()."
|
696
|
+
}
|
697
|
+
},
|
698
|
+
defaultOptions: [],
|
699
|
+
create(context) {
|
700
|
+
const configuration = context.options[0] || {};
|
701
|
+
const restrictedFunctions = configuration.restrictedFunctions || [];
|
702
|
+
const getScope = typeof context.getScope === 'function' ? () => {
|
703
|
+
return context.getScope();
|
704
|
+
} : node => {
|
705
|
+
return context.sourceCode.getScope(node);
|
706
|
+
};
|
707
|
+
function isTopLevelScoped(node) {
|
708
|
+
return getScope(node).block.type === 'Program';
|
709
|
+
}
|
710
|
+
function isThenCatchFinally(node) {
|
711
|
+
return node.property && (node.property.name === 'then' || node.property.name === 'catch' || node.property.name === 'finally');
|
712
|
+
}
|
713
|
+
return {
|
714
|
+
["".concat(AST_NODE_TYPES.CallExpression, " > ").concat(AST_NODE_TYPES.MemberExpression, ".callee")]: node => {
|
715
|
+
if (isTopLevelScoped(node)) {
|
716
|
+
return;
|
717
|
+
}
|
718
|
+
if (!isThenCatchFinally(node)) {
|
719
|
+
return;
|
720
|
+
}
|
721
|
+
const callExpression = node.object;
|
722
|
+
if (callExpression.type === AST_NODE_TYPES.CallExpression && callExpression.callee.type === AST_NODE_TYPES.Identifier && restrictedFunctions.includes(callExpression.callee.name)) {
|
723
|
+
context.report({
|
724
|
+
node: node.property,
|
725
|
+
messageId: 'forbiddenThenCatchFinally',
|
726
|
+
data: {
|
727
|
+
name: callExpression.callee.name
|
728
|
+
}
|
729
|
+
});
|
730
|
+
}
|
731
|
+
}
|
732
|
+
};
|
733
|
+
}
|
734
|
+
});
|
735
|
+
|
736
|
+
const RULE_NAME$6 = 'no-unnecessary-template-literals';
|
737
|
+
var noUnnecessaryTemplateLiterals = createRule({
|
738
|
+
name: RULE_NAME$6,
|
739
|
+
meta: {
|
740
|
+
type: 'problem',
|
741
|
+
docs: {
|
742
|
+
description: 'Check if a template string contains only one ${}',
|
743
|
+
recommended: 'recommended'
|
744
|
+
},
|
745
|
+
fixable: 'code',
|
746
|
+
schema: [],
|
747
|
+
messages: {
|
748
|
+
unnecessaryTemplateString: 'Unnecessary template string with only one ${}.'
|
749
|
+
}
|
750
|
+
},
|
751
|
+
defaultOptions: [],
|
752
|
+
create(context) {
|
753
|
+
return {
|
754
|
+
TemplateLiteral(node) {
|
755
|
+
const code = context.sourceCode.getText(node);
|
756
|
+
if (code.startsWith('`${') && code.endsWith('}`') && code.split('${').length === 2) {
|
757
|
+
context.report({
|
758
|
+
node,
|
759
|
+
messageId: 'unnecessaryTemplateString',
|
760
|
+
fix(fixer) {
|
761
|
+
return fixer.replaceText(node, code.substring(3, code.length - 2));
|
762
|
+
}
|
763
|
+
});
|
764
|
+
}
|
765
|
+
}
|
766
|
+
};
|
767
|
+
}
|
768
|
+
});
|
769
|
+
|
770
|
+
var reactBetterExhaustiveDeps = {
|
771
|
+
meta: {
|
772
|
+
type: 'suggestion',
|
773
|
+
docs: {
|
774
|
+
description: 'verifies the list of dependencies for Hooks like useEffect and similar',
|
775
|
+
url: 'https://github.com/facebook/react/issues/14920'
|
776
|
+
},
|
777
|
+
fixable: 'code',
|
778
|
+
hasSuggestions: true,
|
779
|
+
schema: [{
|
780
|
+
type: 'object',
|
781
|
+
additionalProperties: false,
|
782
|
+
enableDangerousAutofixThisMayCauseInfiniteLoops: false,
|
783
|
+
properties: {
|
784
|
+
additionalHooks: {
|
785
|
+
type: 'string'
|
786
|
+
},
|
787
|
+
enableDangerousAutofixThisMayCauseInfiniteLoops: {
|
788
|
+
type: 'boolean'
|
789
|
+
},
|
790
|
+
customHooks: {
|
791
|
+
type: 'object',
|
792
|
+
additionalProperties: {
|
793
|
+
callbackIndex: {
|
794
|
+
type: 'number'
|
795
|
+
}
|
796
|
+
}
|
797
|
+
},
|
798
|
+
staticHooks: {
|
799
|
+
type: 'object',
|
800
|
+
additionalProperties: {
|
801
|
+
oneOf: [{
|
802
|
+
type: 'boolean'
|
803
|
+
}, {
|
804
|
+
type: 'array',
|
805
|
+
items: {
|
806
|
+
type: 'boolean'
|
807
|
+
}
|
808
|
+
}, {
|
809
|
+
type: 'object',
|
810
|
+
additionalProperties: {
|
811
|
+
type: 'boolean'
|
812
|
+
}
|
813
|
+
}]
|
814
|
+
}
|
815
|
+
},
|
816
|
+
checkMemoizedVariableIsStatic: {
|
817
|
+
type: 'boolean'
|
818
|
+
}
|
819
|
+
}
|
820
|
+
}]
|
821
|
+
},
|
822
|
+
create(context) {
|
823
|
+
const additionalHooks = context.options && context.options[0] && context.options[0].additionalHooks ? new RegExp(context.options[0].additionalHooks) : undefined;
|
824
|
+
const enableDangerousAutofixThisMayCauseInfiniteLoops = context.options && context.options[0] && context.options[0].enableDangerousAutofixThisMayCauseInfiniteLoops || false;
|
825
|
+
const customHooks = context.options && context.options[0] && context.options[0].customHooks || {};
|
826
|
+
const staticHooks = context.options && context.options[0] && context.options[0].staticHooks || {};
|
827
|
+
const checkMemoizedVariableIsStatic = context.options && context.options[0] && context.options[0].checkMemoizedVariableIsStatic || false;
|
828
|
+
const options = {
|
829
|
+
additionalHooks,
|
830
|
+
customHooks,
|
831
|
+
staticHooks,
|
832
|
+
checkMemoizedVariableIsStatic
|
833
|
+
};
|
834
|
+
function reportProblem(problem) {
|
835
|
+
if (enableDangerousAutofixThisMayCauseInfiniteLoops &&
|
836
|
+
Array.isArray(problem.suggest) && problem.suggest.length > 0) {
|
837
|
+
problem.fix = problem.suggest[0].fix;
|
838
|
+
}
|
839
|
+
context.report(problem);
|
840
|
+
}
|
841
|
+
const getSource = typeof context.getSource === 'function' ? node => {
|
842
|
+
return context.getSource(node);
|
843
|
+
} : node => {
|
844
|
+
return context.sourceCode.getText(node);
|
845
|
+
};
|
846
|
+
const getScope = typeof context.getScope === 'function' ? () => {
|
847
|
+
return context.getScope();
|
848
|
+
} : node => {
|
849
|
+
return context.sourceCode.getScope(node);
|
850
|
+
};
|
851
|
+
const scopeManager = context.getSourceCode().scopeManager;
|
852
|
+
const setStateCallSites = new WeakMap();
|
853
|
+
const stateVariables = new WeakSet();
|
854
|
+
const stableKnownValueCache = new WeakMap();
|
855
|
+
const functionWithoutCapturedValueCache = new WeakMap();
|
856
|
+
const useEffectEventVariables = new WeakSet();
|
857
|
+
function memoizeWithWeakMap(fn, map) {
|
858
|
+
return function (arg) {
|
859
|
+
if (map.has(arg)) {
|
860
|
+
return map.get(arg);
|
861
|
+
}
|
862
|
+
const result = fn(arg);
|
863
|
+
map.set(arg, result);
|
864
|
+
return result;
|
865
|
+
};
|
866
|
+
}
|
867
|
+
function visitFunctionWithDependencies(node, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect) {
|
868
|
+
if (isEffect && node.async) {
|
869
|
+
let isCustomHook = false;
|
870
|
+
if (options.customHooks) {
|
871
|
+
Object.entries(options.customHooks).forEach(([key, customParts]) => {
|
872
|
+
if (typeof customParts === 'object' && new RegExp(key).test(reactiveHookName)) {
|
873
|
+
isCustomHook = true;
|
874
|
+
}
|
875
|
+
});
|
876
|
+
}
|
877
|
+
if (!isCustomHook) {
|
878
|
+
reportProblem({
|
879
|
+
node: node,
|
880
|
+
message: "Effect callbacks are synchronous to prevent race conditions. " + "Put the async function inside:\n\n" + 'useEffect(() => {\n' + ' async function fetchData() {\n' + ' // You can await here\n' + ' const response = await MyAPI.getData(someId);\n' + ' // ...\n' + ' }\n' + ' fetchData();\n' + "}, [someId]); // Or [] if effect doesn't need props or state\n\n" + 'Learn more about data fetching with Hooks: https://react.dev/link/hooks-data-fetching'
|
881
|
+
});
|
882
|
+
}
|
883
|
+
}
|
884
|
+
const scope = scopeManager.acquire(node);
|
885
|
+
const pureScopes = new Set();
|
886
|
+
let componentScope = null;
|
887
|
+
{
|
888
|
+
let currentScope = scope.upper;
|
889
|
+
while (currentScope) {
|
890
|
+
pureScopes.add(currentScope);
|
891
|
+
if (currentScope.type === 'function') {
|
892
|
+
break;
|
893
|
+
}
|
894
|
+
currentScope = currentScope.upper;
|
895
|
+
}
|
896
|
+
if (!currentScope) {
|
897
|
+
return;
|
898
|
+
}
|
899
|
+
componentScope = currentScope;
|
900
|
+
}
|
901
|
+
const isArray = Array.isArray;
|
902
|
+
const memoizedIsStableKnownHookValue = memoizeWithWeakMap(isStableKnownHookValue, stableKnownValueCache);
|
903
|
+
const memoizedIsFunctionWithoutCapturedValues = memoizeWithWeakMap(isFunctionWithoutCapturedValues, functionWithoutCapturedValueCache);
|
904
|
+
function isStableKnownHookValue(resolved) {
|
905
|
+
if (!isArray(resolved.defs)) {
|
906
|
+
return false;
|
907
|
+
}
|
908
|
+
const def = resolved.defs[0];
|
909
|
+
if (def == null) {
|
910
|
+
return false;
|
911
|
+
}
|
912
|
+
if (def.node.type !== 'VariableDeclarator') {
|
913
|
+
return false;
|
914
|
+
}
|
915
|
+
let init = def.node.init;
|
916
|
+
if (init == null) {
|
917
|
+
return false;
|
918
|
+
}
|
919
|
+
while (init.type === 'TSAsExpression' || init.type === 'AsExpression') {
|
920
|
+
init = init.expression;
|
921
|
+
}
|
922
|
+
let declaration = def.node.parent;
|
923
|
+
if (declaration == null) {
|
924
|
+
fastFindReferenceWithParent(componentScope.block, def.node.id);
|
925
|
+
declaration = def.node.parent;
|
926
|
+
if (declaration == null) {
|
927
|
+
return false;
|
928
|
+
}
|
929
|
+
}
|
930
|
+
if (declaration.kind === 'const' && init.type === 'Literal' && (typeof init.value === 'string' || typeof init.value === 'number' || init.value == null)) {
|
931
|
+
return true;
|
932
|
+
}
|
933
|
+
if (init.type !== 'CallExpression') {
|
934
|
+
return false;
|
935
|
+
}
|
936
|
+
let callee = init.callee;
|
937
|
+
if (callee.type === 'MemberExpression' && callee.object.name === 'React' && callee.property != null && !callee.computed) {
|
938
|
+
callee = callee.property;
|
939
|
+
}
|
940
|
+
if (callee.type !== 'Identifier') {
|
941
|
+
return false;
|
942
|
+
}
|
943
|
+
const id = def.node.id;
|
944
|
+
const {
|
945
|
+
name
|
946
|
+
} = callee;
|
947
|
+
if (name === 'useRef' && id.type === 'Identifier') {
|
948
|
+
return true;
|
949
|
+
} else if (isUseEffectEventIdentifier() && id.type === 'Identifier') {
|
950
|
+
for (const ref of resolved.references) {
|
951
|
+
if (ref !== id) {
|
952
|
+
useEffectEventVariables.add(ref.identifier);
|
953
|
+
}
|
954
|
+
}
|
955
|
+
return true;
|
956
|
+
} else if (name === 'useState' || name === 'useReducer' || name === 'useActionState') {
|
957
|
+
if (id.type === 'ArrayPattern' && id.elements.length === 2 && isArray(resolved.identifiers)) {
|
958
|
+
if (id.elements[1] === resolved.identifiers[0]) {
|
959
|
+
if (name === 'useState') {
|
960
|
+
const references = resolved.references;
|
961
|
+
let writeCount = 0;
|
962
|
+
for (const reference of references) {
|
963
|
+
if (reference.isWrite()) {
|
964
|
+
writeCount++;
|
965
|
+
}
|
966
|
+
if (writeCount > 1) {
|
967
|
+
return false;
|
968
|
+
}
|
969
|
+
setStateCallSites.set(reference.identifier, id.elements[0]);
|
970
|
+
}
|
971
|
+
}
|
972
|
+
return true;
|
973
|
+
} else if (id.elements[0] === resolved.identifiers[0]) {
|
974
|
+
if (name === 'useState') {
|
975
|
+
const references = resolved.references;
|
976
|
+
for (const reference of references) {
|
977
|
+
stateVariables.add(reference.identifier);
|
978
|
+
}
|
979
|
+
}
|
980
|
+
return false;
|
981
|
+
}
|
982
|
+
}
|
983
|
+
} else if (name === 'useTransition') {
|
984
|
+
if (id.type === 'ArrayPattern' && id.elements.length === 2 && Array.isArray(resolved.identifiers) &&
|
985
|
+
id.elements[1] === resolved.identifiers[0]) {
|
986
|
+
return true;
|
987
|
+
}
|
988
|
+
} else if (options.checkMemoizedVariableIsStatic && (name === 'useMemo' || name === 'useCallback')) {
|
989
|
+
const hookArgs = callee.parent.arguments;
|
990
|
+
if (hookArgs.length < 2) {
|
991
|
+
return false;
|
992
|
+
}
|
993
|
+
const dependencies = hookArgs[1].elements;
|
994
|
+
if (dependencies.length === 0) {
|
995
|
+
return true;
|
996
|
+
}
|
997
|
+
for (const dependencyNode of dependencies) {
|
998
|
+
const dependencyRefernece = resolved.scope.references.find(reference => reference.identifier === dependencyNode);
|
999
|
+
if (dependencyRefernece !== undefined && memoizedIsStableKnownHookValue(dependencyRefernece.resolved)) {
|
1000
|
+
continue;
|
1001
|
+
} else {
|
1002
|
+
return false;
|
1003
|
+
}
|
1004
|
+
}
|
1005
|
+
return true;
|
1006
|
+
} else {
|
1007
|
+
Object.entries(options.staticHooks).forEach(([key, staticParts]) => {
|
1008
|
+
if (typeof staticParts === 'object' && staticParts.regexp && new RegExp(key).test(name)) {
|
1009
|
+
options.staticHooks[name] = staticParts.value;
|
1010
|
+
}
|
1011
|
+
});
|
1012
|
+
if (options.staticHooks[name]) {
|
1013
|
+
const staticParts = options.staticHooks[name];
|
1014
|
+
if (staticParts === true) {
|
1015
|
+
return true;
|
1016
|
+
} else if (Array.isArray(staticParts)) {
|
1017
|
+
if (id.type === 'ArrayPattern' && id.elements.length <= staticParts.length && Array.isArray(resolved.identifiers)) {
|
1018
|
+
const idx = id.elements.indexOf(resolved.identifiers[0]);
|
1019
|
+
if (idx !== -1) {
|
1020
|
+
return staticParts[idx];
|
1021
|
+
}
|
1022
|
+
}
|
1023
|
+
} else if (typeof staticParts === 'object' && id.type === 'ObjectPattern') {
|
1024
|
+
const property = id.properties.find(p => p.key === resolved.identifiers[0]);
|
1025
|
+
if (property) {
|
1026
|
+
return staticParts[property.key.name];
|
1027
|
+
}
|
1028
|
+
}
|
1029
|
+
}
|
1030
|
+
}
|
1031
|
+
return false;
|
1032
|
+
}
|
1033
|
+
function isFunctionWithoutCapturedValues(resolved) {
|
1034
|
+
if (!isArray(resolved.defs)) {
|
1035
|
+
return false;
|
1036
|
+
}
|
1037
|
+
const def = resolved.defs[0];
|
1038
|
+
if (def == null) {
|
1039
|
+
return false;
|
1040
|
+
}
|
1041
|
+
if (def.node == null || def.node.id == null) {
|
1042
|
+
return false;
|
1043
|
+
}
|
1044
|
+
const fnNode = def.node;
|
1045
|
+
const childScopes = componentScope.childScopes;
|
1046
|
+
let fnScope = null;
|
1047
|
+
let i;
|
1048
|
+
for (i = 0; i < childScopes.length; i++) {
|
1049
|
+
const childScope = childScopes[i];
|
1050
|
+
const childScopeBlock = childScope.block;
|
1051
|
+
if (
|
1052
|
+
fnNode.type === 'FunctionDeclaration' && childScopeBlock === fnNode ||
|
1053
|
+
fnNode.type === 'VariableDeclarator' && childScopeBlock.parent === fnNode) {
|
1054
|
+
fnScope = childScope;
|
1055
|
+
break;
|
1056
|
+
}
|
1057
|
+
}
|
1058
|
+
if (fnScope == null) {
|
1059
|
+
return false;
|
1060
|
+
}
|
1061
|
+
for (i = 0; i < fnScope.through.length; i++) {
|
1062
|
+
const ref = fnScope.through[i];
|
1063
|
+
if (ref.resolved == null) {
|
1064
|
+
continue;
|
1065
|
+
}
|
1066
|
+
if (pureScopes.has(ref.resolved.scope) &&
|
1067
|
+
!memoizedIsStableKnownHookValue(ref.resolved)) {
|
1068
|
+
return false;
|
1069
|
+
}
|
1070
|
+
}
|
1071
|
+
return true;
|
1072
|
+
}
|
1073
|
+
const currentRefsInEffectCleanup = new Map();
|
1074
|
+
function isInsideEffectCleanup(reference) {
|
1075
|
+
let curScope = reference.from;
|
1076
|
+
let isInReturnedFunction = false;
|
1077
|
+
while (curScope.block !== node) {
|
1078
|
+
if (curScope.type === 'function') {
|
1079
|
+
isInReturnedFunction = curScope.block.parent != null && curScope.block.parent.type === 'ReturnStatement';
|
1080
|
+
}
|
1081
|
+
curScope = curScope.upper;
|
1082
|
+
}
|
1083
|
+
return isInReturnedFunction;
|
1084
|
+
}
|
1085
|
+
const dependencies = new Map();
|
1086
|
+
const optionalChains = new Map();
|
1087
|
+
gatherDependenciesRecursively(scope);
|
1088
|
+
function gatherDependenciesRecursively(currentScope) {
|
1089
|
+
for (const reference of currentScope.references) {
|
1090
|
+
if (!reference.resolved) {
|
1091
|
+
continue;
|
1092
|
+
}
|
1093
|
+
if (!pureScopes.has(reference.resolved.scope)) {
|
1094
|
+
continue;
|
1095
|
+
}
|
1096
|
+
const referenceNode = fastFindReferenceWithParent(node, reference.identifier);
|
1097
|
+
const dependencyNode = getDependency(referenceNode);
|
1098
|
+
const dependency = analyzePropertyChain(dependencyNode, optionalChains);
|
1099
|
+
if (
|
1100
|
+
isEffect &&
|
1101
|
+
dependencyNode.type === 'Identifier' && (dependencyNode.parent.type === 'MemberExpression' || dependencyNode.parent.type === 'OptionalMemberExpression') && !dependencyNode.parent.computed && dependencyNode.parent.property.type === 'Identifier' && dependencyNode.parent.property.name === 'current' &&
|
1102
|
+
isInsideEffectCleanup(reference)) {
|
1103
|
+
currentRefsInEffectCleanup.set(dependency, {
|
1104
|
+
reference,
|
1105
|
+
dependencyNode
|
1106
|
+
});
|
1107
|
+
}
|
1108
|
+
if (dependencyNode.parent.type === 'TSTypeQuery' || dependencyNode.parent.type === 'TSTypeReference') {
|
1109
|
+
continue;
|
1110
|
+
}
|
1111
|
+
const def = reference.resolved.defs[0];
|
1112
|
+
if (def == null) {
|
1113
|
+
continue;
|
1114
|
+
}
|
1115
|
+
if (def.node != null && def.node.init === node.parent) {
|
1116
|
+
continue;
|
1117
|
+
}
|
1118
|
+
if (def.type === 'TypeParameter') {
|
1119
|
+
continue;
|
1120
|
+
}
|
1121
|
+
if (!dependencies.has(dependency)) {
|
1122
|
+
const resolved = reference.resolved;
|
1123
|
+
const isStable = memoizedIsStableKnownHookValue(resolved) || memoizedIsFunctionWithoutCapturedValues(resolved);
|
1124
|
+
dependencies.set(dependency, {
|
1125
|
+
isStable,
|
1126
|
+
references: [reference]
|
1127
|
+
});
|
1128
|
+
} else {
|
1129
|
+
dependencies.get(dependency).references.push(reference);
|
1130
|
+
}
|
1131
|
+
}
|
1132
|
+
for (const childScope of currentScope.childScopes) {
|
1133
|
+
gatherDependenciesRecursively(childScope);
|
1134
|
+
}
|
1135
|
+
}
|
1136
|
+
currentRefsInEffectCleanup.forEach(({
|
1137
|
+
reference,
|
1138
|
+
dependencyNode
|
1139
|
+
}, dependency) => {
|
1140
|
+
const references = reference.resolved.references;
|
1141
|
+
let foundCurrentAssignment = false;
|
1142
|
+
for (const {
|
1143
|
+
identifier
|
1144
|
+
} of references) {
|
1145
|
+
const {
|
1146
|
+
parent
|
1147
|
+
} = identifier;
|
1148
|
+
if (parent != null &&
|
1149
|
+
parent.type === 'MemberExpression' && !parent.computed && parent.property.type === 'Identifier' && parent.property.name === 'current' &&
|
1150
|
+
parent.parent.type === 'AssignmentExpression' && parent.parent.left === parent) {
|
1151
|
+
foundCurrentAssignment = true;
|
1152
|
+
break;
|
1153
|
+
}
|
1154
|
+
}
|
1155
|
+
if (foundCurrentAssignment) {
|
1156
|
+
return;
|
1157
|
+
}
|
1158
|
+
reportProblem({
|
1159
|
+
node: dependencyNode.parent.property,
|
1160
|
+
message: "The ref value '".concat(dependency, ".current' will likely have ") + "changed by the time this effect cleanup function runs. If " + "this ref points to a node rendered by React, copy " + "'".concat(dependency, ".current' to a variable inside the effect, and ") + "use that variable in the cleanup function."
|
1161
|
+
});
|
1162
|
+
});
|
1163
|
+
const staleAssignments = new Set();
|
1164
|
+
function reportStaleAssignment(writeExpr, key) {
|
1165
|
+
if (staleAssignments.has(key)) {
|
1166
|
+
return;
|
1167
|
+
}
|
1168
|
+
staleAssignments.add(key);
|
1169
|
+
reportProblem({
|
1170
|
+
node: writeExpr,
|
1171
|
+
message: "Assignments to the '".concat(key, "' variable from inside React Hook ") + "".concat(getSource(reactiveHook), " will be lost after each ") + "render. To preserve the value over time, store it in a useRef " + "Hook and keep the mutable value in the '.current' property. " + "Otherwise, you can move this variable directly inside " + "".concat(getSource(reactiveHook), ".")
|
1172
|
+
});
|
1173
|
+
}
|
1174
|
+
const stableDependencies = new Set();
|
1175
|
+
dependencies.forEach(({
|
1176
|
+
isStable,
|
1177
|
+
references
|
1178
|
+
}, key) => {
|
1179
|
+
if (isStable) {
|
1180
|
+
stableDependencies.add(key);
|
1181
|
+
}
|
1182
|
+
references.forEach(reference => {
|
1183
|
+
if (reference.writeExpr) {
|
1184
|
+
reportStaleAssignment(reference.writeExpr, key);
|
1185
|
+
}
|
1186
|
+
});
|
1187
|
+
});
|
1188
|
+
if (staleAssignments.size > 0) {
|
1189
|
+
return;
|
1190
|
+
}
|
1191
|
+
if (!declaredDependenciesNode) {
|
1192
|
+
let setStateInsideEffectWithoutDeps = null;
|
1193
|
+
dependencies.forEach(({
|
1194
|
+
references
|
1195
|
+
}, key) => {
|
1196
|
+
if (setStateInsideEffectWithoutDeps) {
|
1197
|
+
return;
|
1198
|
+
}
|
1199
|
+
references.forEach(reference => {
|
1200
|
+
if (setStateInsideEffectWithoutDeps) {
|
1201
|
+
return;
|
1202
|
+
}
|
1203
|
+
const id = reference.identifier;
|
1204
|
+
const isSetState = setStateCallSites.has(id);
|
1205
|
+
if (!isSetState) {
|
1206
|
+
return;
|
1207
|
+
}
|
1208
|
+
let fnScope = reference.from;
|
1209
|
+
while (fnScope.type !== 'function') {
|
1210
|
+
fnScope = fnScope.upper;
|
1211
|
+
}
|
1212
|
+
const isDirectlyInsideEffect = fnScope.block === node;
|
1213
|
+
if (isDirectlyInsideEffect) {
|
1214
|
+
setStateInsideEffectWithoutDeps = key;
|
1215
|
+
}
|
1216
|
+
});
|
1217
|
+
});
|
1218
|
+
if (setStateInsideEffectWithoutDeps) {
|
1219
|
+
const {
|
1220
|
+
suggestedDependencies
|
1221
|
+
} = collectRecommendations({
|
1222
|
+
dependencies,
|
1223
|
+
declaredDependencies: [],
|
1224
|
+
stableDependencies,
|
1225
|
+
externalDependencies: new Set(),
|
1226
|
+
isEffect: true
|
1227
|
+
});
|
1228
|
+
reportProblem({
|
1229
|
+
node: reactiveHook,
|
1230
|
+
message: "React Hook ".concat(reactiveHookName, " contains a call to '").concat(setStateInsideEffectWithoutDeps, "'. ") + "Without a list of dependencies, this can lead to an infinite chain of updates. " + "To fix this, pass [" + suggestedDependencies.join(', ') + "] as a second argument to the ".concat(reactiveHookName, " Hook."),
|
1231
|
+
suggest: [{
|
1232
|
+
desc: "Add dependencies array: [".concat(suggestedDependencies.join(', '), "]"),
|
1233
|
+
fix(fixer) {
|
1234
|
+
return fixer.insertTextAfter(node, ", [".concat(suggestedDependencies.join(', '), "]"));
|
1235
|
+
}
|
1236
|
+
}]
|
1237
|
+
});
|
1238
|
+
}
|
1239
|
+
return;
|
1240
|
+
}
|
1241
|
+
const declaredDependencies = [];
|
1242
|
+
const externalDependencies = new Set();
|
1243
|
+
const isArrayExpression = declaredDependenciesNode.type === 'ArrayExpression';
|
1244
|
+
const isTSAsArrayExpression = declaredDependenciesNode.type === 'TSAsExpression' && declaredDependenciesNode.expression.type === 'ArrayExpression';
|
1245
|
+
if (!isArrayExpression && !isTSAsArrayExpression) {
|
1246
|
+
reportProblem({
|
1247
|
+
node: declaredDependenciesNode,
|
1248
|
+
message: "React Hook ".concat(getSource(reactiveHook), " was passed a ") + 'dependency list that is not an array literal. This means we ' + "can't statically verify whether you've passed the correct " + 'dependencies.'
|
1249
|
+
});
|
1250
|
+
} else {
|
1251
|
+
const arrayExpression = isTSAsArrayExpression ? declaredDependenciesNode.expression : declaredDependenciesNode;
|
1252
|
+
arrayExpression.elements.forEach(declaredDependencyNode => {
|
1253
|
+
if (declaredDependencyNode == null) {
|
1254
|
+
return;
|
1255
|
+
}
|
1256
|
+
if (declaredDependencyNode.type === 'SpreadElement') {
|
1257
|
+
reportProblem({
|
1258
|
+
node: declaredDependencyNode,
|
1259
|
+
message: "React Hook ".concat(getSource(reactiveHook), " has a spread ") + "element in its dependency array. This means we can't " + "statically verify whether you've passed the " + 'correct dependencies.'
|
1260
|
+
});
|
1261
|
+
return;
|
1262
|
+
}
|
1263
|
+
if (useEffectEventVariables.has(declaredDependencyNode)) {
|
1264
|
+
reportProblem({
|
1265
|
+
node: declaredDependencyNode,
|
1266
|
+
message: 'Functions returned from `useEffectEvent` must not be included in the dependency array. ' + "Remove `".concat(getSource(declaredDependencyNode), "` from the list."),
|
1267
|
+
suggest: [{
|
1268
|
+
desc: "Remove the dependency `".concat(getSource(declaredDependencyNode), "`"),
|
1269
|
+
fix(fixer) {
|
1270
|
+
return fixer.removeRange(declaredDependencyNode.range);
|
1271
|
+
}
|
1272
|
+
}]
|
1273
|
+
});
|
1274
|
+
}
|
1275
|
+
let declaredDependency;
|
1276
|
+
try {
|
1277
|
+
declaredDependency = analyzePropertyChain(declaredDependencyNode, null);
|
1278
|
+
} catch (err) {
|
1279
|
+
if (/Unsupported node type/.test(err.message)) {
|
1280
|
+
if (declaredDependencyNode.type === 'Literal') {
|
1281
|
+
if (dependencies.has(declaredDependencyNode.value)) {
|
1282
|
+
reportProblem({
|
1283
|
+
node: declaredDependencyNode,
|
1284
|
+
message: "The ".concat(declaredDependencyNode.raw, " literal is not a valid dependency ") + "because it never changes. " + "Did you mean to include ".concat(declaredDependencyNode.value, " in the array instead?")
|
1285
|
+
});
|
1286
|
+
} else {
|
1287
|
+
reportProblem({
|
1288
|
+
node: declaredDependencyNode,
|
1289
|
+
message: "The ".concat(declaredDependencyNode.raw, " literal is not a valid dependency ") + 'because it never changes. You can safely remove it.'
|
1290
|
+
});
|
1291
|
+
}
|
1292
|
+
} else {
|
1293
|
+
reportProblem({
|
1294
|
+
node: declaredDependencyNode,
|
1295
|
+
message: "React Hook ".concat(getSource(reactiveHook), " has a ") + "complex expression in the dependency array. " + 'Extract it to a separate variable so it can be statically checked.'
|
1296
|
+
});
|
1297
|
+
}
|
1298
|
+
return;
|
1299
|
+
}
|
1300
|
+
throw err;
|
1301
|
+
}
|
1302
|
+
let maybeID = declaredDependencyNode;
|
1303
|
+
while (maybeID.type === 'MemberExpression' || maybeID.type === 'OptionalMemberExpression' || maybeID.type === 'ChainExpression') {
|
1304
|
+
maybeID = maybeID.object || maybeID.expression.object;
|
1305
|
+
}
|
1306
|
+
const isDeclaredInComponent = !componentScope.through.some(ref => ref.identifier === maybeID);
|
1307
|
+
declaredDependencies.push({
|
1308
|
+
key: declaredDependency,
|
1309
|
+
node: declaredDependencyNode
|
1310
|
+
});
|
1311
|
+
if (!isDeclaredInComponent) {
|
1312
|
+
externalDependencies.add(declaredDependency);
|
1313
|
+
}
|
1314
|
+
});
|
1315
|
+
}
|
1316
|
+
const {
|
1317
|
+
suggestedDependencies,
|
1318
|
+
unnecessaryDependencies,
|
1319
|
+
missingDependencies,
|
1320
|
+
duplicateDependencies
|
1321
|
+
} = collectRecommendations({
|
1322
|
+
dependencies,
|
1323
|
+
declaredDependencies,
|
1324
|
+
stableDependencies,
|
1325
|
+
externalDependencies,
|
1326
|
+
isEffect
|
1327
|
+
});
|
1328
|
+
let suggestedDeps = suggestedDependencies;
|
1329
|
+
const problemCount = duplicateDependencies.size + missingDependencies.size + unnecessaryDependencies.size;
|
1330
|
+
if (problemCount === 0) {
|
1331
|
+
const constructions = scanForConstructions({
|
1332
|
+
declaredDependencies,
|
1333
|
+
declaredDependenciesNode,
|
1334
|
+
componentScope,
|
1335
|
+
scope
|
1336
|
+
});
|
1337
|
+
constructions.forEach(({
|
1338
|
+
construction,
|
1339
|
+
isUsedOutsideOfHook,
|
1340
|
+
depType
|
1341
|
+
}) => {
|
1342
|
+
const wrapperHook = depType === 'function' ? 'useCallback' : 'useMemo';
|
1343
|
+
const constructionType = depType === 'function' ? 'definition' : 'initialization';
|
1344
|
+
const defaultAdvice = "wrap the ".concat(constructionType, " of '").concat(construction.name.name, "' in its own ").concat(wrapperHook, "() Hook.");
|
1345
|
+
const advice = isUsedOutsideOfHook ? "To fix this, ".concat(defaultAdvice) : "Move it inside the ".concat(reactiveHookName, " callback. Alternatively, ").concat(defaultAdvice);
|
1346
|
+
const causation = depType === 'conditional' || depType === 'logical expression' ? 'could make' : 'makes';
|
1347
|
+
const message = "The '".concat(construction.name.name, "' ").concat(depType, " ").concat(causation, " the dependencies of ") + "".concat(reactiveHookName, " Hook (at line ").concat(declaredDependenciesNode.loc.start.line, ") ") + "change on every render. ".concat(advice);
|
1348
|
+
let suggest;
|
1349
|
+
if (isUsedOutsideOfHook && construction.type === 'Variable' &&
|
1350
|
+
depType === 'function') {
|
1351
|
+
suggest = [{
|
1352
|
+
desc: "Wrap the ".concat(constructionType, " of '").concat(construction.name.name, "' in its own ").concat(wrapperHook, "() Hook."),
|
1353
|
+
fix(fixer) {
|
1354
|
+
const [before, after] = wrapperHook === 'useMemo' ? ["useMemo(() => { return ", '; })'] : ['useCallback(', ')'];
|
1355
|
+
return [
|
1356
|
+
fixer.insertTextBefore(construction.node.init, before),
|
1357
|
+
fixer.insertTextAfter(construction.node.init, after)];
|
1358
|
+
}
|
1359
|
+
}];
|
1360
|
+
}
|
1361
|
+
reportProblem({
|
1362
|
+
node: construction.node,
|
1363
|
+
message,
|
1364
|
+
suggest
|
1365
|
+
});
|
1366
|
+
});
|
1367
|
+
return;
|
1368
|
+
}
|
1369
|
+
if (!isEffect && missingDependencies.size > 0) {
|
1370
|
+
suggestedDeps = collectRecommendations({
|
1371
|
+
dependencies,
|
1372
|
+
declaredDependencies: [],
|
1373
|
+
stableDependencies,
|
1374
|
+
externalDependencies,
|
1375
|
+
isEffect
|
1376
|
+
}).suggestedDependencies;
|
1377
|
+
}
|
1378
|
+
function areDeclaredDepsAlphabetized() {
|
1379
|
+
if (declaredDependencies.length === 0) {
|
1380
|
+
return true;
|
1381
|
+
}
|
1382
|
+
const declaredDepKeys = declaredDependencies.map(dep => dep.key);
|
1383
|
+
const sortedDeclaredDepKeys = [...declaredDepKeys].sort();
|
1384
|
+
return declaredDepKeys.join(',') === sortedDeclaredDepKeys.join(',');
|
1385
|
+
}
|
1386
|
+
if (areDeclaredDepsAlphabetized()) {
|
1387
|
+
suggestedDeps.sort();
|
1388
|
+
}
|
1389
|
+
function formatDependency(path) {
|
1390
|
+
const members = path.split('.');
|
1391
|
+
let finalPath = '';
|
1392
|
+
for (let i = 0; i < members.length; i++) {
|
1393
|
+
if (i !== 0) {
|
1394
|
+
const pathSoFar = members.slice(0, i + 1).join('.');
|
1395
|
+
const isOptional = optionalChains.get(pathSoFar) === true;
|
1396
|
+
finalPath += isOptional ? '?.' : '.';
|
1397
|
+
}
|
1398
|
+
finalPath += members[i];
|
1399
|
+
}
|
1400
|
+
return finalPath;
|
1401
|
+
}
|
1402
|
+
function getWarningMessage(deps, singlePrefix, label, fixVerb) {
|
1403
|
+
if (deps.size === 0) {
|
1404
|
+
return null;
|
1405
|
+
}
|
1406
|
+
return (deps.size > 1 ? '' : singlePrefix + ' ') + label + ' ' + (deps.size > 1 ? 'dependencies' : 'dependency') + ': ' + joinEnglish([...deps].sort().map(name => "'" + formatDependency(name) + "'")) + ". Either ".concat(fixVerb, " ").concat(deps.size > 1 ? 'them' : 'it', " or remove the dependency array.");
|
1407
|
+
}
|
1408
|
+
let extraWarning = '';
|
1409
|
+
if (unnecessaryDependencies.size > 0) {
|
1410
|
+
let badRef = null;
|
1411
|
+
[...unnecessaryDependencies.keys()].forEach(key => {
|
1412
|
+
if (badRef != null) {
|
1413
|
+
return;
|
1414
|
+
}
|
1415
|
+
if (key.endsWith('.current')) {
|
1416
|
+
badRef = key;
|
1417
|
+
}
|
1418
|
+
});
|
1419
|
+
if (badRef != null) {
|
1420
|
+
extraWarning = " Mutable values like '".concat(badRef, "' aren't valid dependencies ") + "because mutating them doesn't re-render the component.";
|
1421
|
+
} else if (externalDependencies.size > 0) {
|
1422
|
+
const dep = [...externalDependencies][0];
|
1423
|
+
if (!scope.set.has(dep)) {
|
1424
|
+
extraWarning = " Outer scope values like '".concat(dep, "' aren't valid dependencies ") + "because mutating them doesn't re-render the component.";
|
1425
|
+
}
|
1426
|
+
}
|
1427
|
+
}
|
1428
|
+
if (!extraWarning && missingDependencies.has('props')) {
|
1429
|
+
const propDep = dependencies.get('props');
|
1430
|
+
if (propDep == null) {
|
1431
|
+
return;
|
1432
|
+
}
|
1433
|
+
const refs = propDep.references;
|
1434
|
+
if (!Array.isArray(refs)) {
|
1435
|
+
return;
|
1436
|
+
}
|
1437
|
+
let isPropsOnlyUsedInMembers = true;
|
1438
|
+
for (const ref of refs) {
|
1439
|
+
const id = fastFindReferenceWithParent(componentScope.block, ref.identifier);
|
1440
|
+
if (!id) {
|
1441
|
+
isPropsOnlyUsedInMembers = false;
|
1442
|
+
break;
|
1443
|
+
}
|
1444
|
+
const parent = id.parent;
|
1445
|
+
if (parent == null) {
|
1446
|
+
isPropsOnlyUsedInMembers = false;
|
1447
|
+
break;
|
1448
|
+
}
|
1449
|
+
if (parent.type !== 'MemberExpression' && parent.type !== 'OptionalMemberExpression') {
|
1450
|
+
isPropsOnlyUsedInMembers = false;
|
1451
|
+
break;
|
1452
|
+
}
|
1453
|
+
}
|
1454
|
+
if (isPropsOnlyUsedInMembers) {
|
1455
|
+
extraWarning = " However, 'props' will change when *any* prop changes, so the " + "preferred fix is to destructure the 'props' object outside of " + "the ".concat(reactiveHookName, " call and refer to those specific props ") + "inside ".concat(getSource(reactiveHook), ".");
|
1456
|
+
}
|
1457
|
+
}
|
1458
|
+
if (!extraWarning && missingDependencies.size > 0) {
|
1459
|
+
let missingCallbackDep = null;
|
1460
|
+
missingDependencies.forEach(missingDep => {
|
1461
|
+
if (missingCallbackDep) {
|
1462
|
+
return;
|
1463
|
+
}
|
1464
|
+
const topScopeRef = componentScope.set.get(missingDep);
|
1465
|
+
const usedDep = dependencies.get(missingDep);
|
1466
|
+
if (usedDep.references[0].resolved !== topScopeRef) {
|
1467
|
+
return;
|
1468
|
+
}
|
1469
|
+
const def = topScopeRef.defs[0];
|
1470
|
+
if (def == null || def.name == null || def.type !== 'Parameter') {
|
1471
|
+
return;
|
1472
|
+
}
|
1473
|
+
let isFunctionCall = false;
|
1474
|
+
let id;
|
1475
|
+
for (let i = 0; i < usedDep.references.length; i++) {
|
1476
|
+
id = usedDep.references[i].identifier;
|
1477
|
+
if (id != null && id.parent != null && (id.parent.type === 'CallExpression' || id.parent.type === 'OptionalCallExpression') && id.parent.callee === id) {
|
1478
|
+
isFunctionCall = true;
|
1479
|
+
break;
|
1480
|
+
}
|
1481
|
+
}
|
1482
|
+
if (!isFunctionCall) {
|
1483
|
+
return;
|
1484
|
+
}
|
1485
|
+
missingCallbackDep = missingDep;
|
1486
|
+
});
|
1487
|
+
if (missingCallbackDep != null) {
|
1488
|
+
extraWarning = " If '".concat(missingCallbackDep, "' changes too often, ") + "find the parent component that defines it " + "and wrap that definition in useCallback.";
|
1489
|
+
}
|
1490
|
+
}
|
1491
|
+
if (!extraWarning && missingDependencies.size > 0) {
|
1492
|
+
let setStateRecommendation = null;
|
1493
|
+
missingDependencies.forEach(missingDep => {
|
1494
|
+
if (setStateRecommendation != null) {
|
1495
|
+
return;
|
1496
|
+
}
|
1497
|
+
const usedDep = dependencies.get(missingDep);
|
1498
|
+
const references = usedDep.references;
|
1499
|
+
let id;
|
1500
|
+
let maybeCall;
|
1501
|
+
for (const reference of references) {
|
1502
|
+
id = reference.identifier;
|
1503
|
+
maybeCall = id.parent;
|
1504
|
+
while (maybeCall != null && maybeCall !== componentScope.block) {
|
1505
|
+
if (maybeCall.type === 'CallExpression') {
|
1506
|
+
const correspondingStateVariable = setStateCallSites.get(maybeCall.callee);
|
1507
|
+
if (correspondingStateVariable != null) {
|
1508
|
+
if (correspondingStateVariable.name === missingDep) {
|
1509
|
+
setStateRecommendation = {
|
1510
|
+
missingDep,
|
1511
|
+
setter: maybeCall.callee.name,
|
1512
|
+
form: 'updater'
|
1513
|
+
};
|
1514
|
+
} else if (stateVariables.has(id)) {
|
1515
|
+
setStateRecommendation = {
|
1516
|
+
missingDep,
|
1517
|
+
setter: maybeCall.callee.name,
|
1518
|
+
form: 'reducer'
|
1519
|
+
};
|
1520
|
+
} else {
|
1521
|
+
const resolved = reference.resolved;
|
1522
|
+
if (resolved != null) {
|
1523
|
+
const def = resolved.defs[0];
|
1524
|
+
if (def != null && def.type === 'Parameter') {
|
1525
|
+
setStateRecommendation = {
|
1526
|
+
missingDep,
|
1527
|
+
setter: maybeCall.callee.name,
|
1528
|
+
form: 'inlineReducer'
|
1529
|
+
};
|
1530
|
+
}
|
1531
|
+
}
|
1532
|
+
}
|
1533
|
+
break;
|
1534
|
+
}
|
1535
|
+
}
|
1536
|
+
maybeCall = maybeCall.parent;
|
1537
|
+
}
|
1538
|
+
if (setStateRecommendation != null) {
|
1539
|
+
break;
|
1540
|
+
}
|
1541
|
+
}
|
1542
|
+
});
|
1543
|
+
if (setStateRecommendation != null) {
|
1544
|
+
switch (setStateRecommendation.form) {
|
1545
|
+
case 'reducer':
|
1546
|
+
extraWarning = " You can also replace multiple useState variables with useReducer " + "if '".concat(setStateRecommendation.setter, "' needs the ") + "current value of '".concat(setStateRecommendation.missingDep, "'.");
|
1547
|
+
break;
|
1548
|
+
case 'inlineReducer':
|
1549
|
+
extraWarning = " If '".concat(setStateRecommendation.setter, "' needs the ") + "current value of '".concat(setStateRecommendation.missingDep, "', ") + "you can also switch to useReducer instead of useState and " + "read '".concat(setStateRecommendation.missingDep, "' in the reducer.");
|
1550
|
+
break;
|
1551
|
+
case 'updater':
|
1552
|
+
extraWarning = " You can also do a functional update '".concat(setStateRecommendation.setter, "(").concat(setStateRecommendation.missingDep.slice(0, 1), " => ...)' if you only need '").concat(setStateRecommendation.missingDep
|
1553
|
+
, "'") + " in the '".concat(setStateRecommendation.setter, "' call.");
|
1554
|
+
break;
|
1555
|
+
default:
|
1556
|
+
throw new Error('Unknown case.');
|
1557
|
+
}
|
1558
|
+
}
|
1559
|
+
}
|
1560
|
+
reportProblem({
|
1561
|
+
node: declaredDependenciesNode,
|
1562
|
+
message: "React Hook ".concat(getSource(reactiveHook), " has ") + (
|
1563
|
+
getWarningMessage(missingDependencies, 'a', 'missing', 'include') || getWarningMessage(unnecessaryDependencies, 'an', 'unnecessary', 'exclude') || getWarningMessage(duplicateDependencies, 'a', 'duplicate', 'omit')) + extraWarning,
|
1564
|
+
suggest: [{
|
1565
|
+
desc: "Update the dependencies array to be: [".concat(suggestedDeps.map(element => formatDependency(element)).join(', '), "]"),
|
1566
|
+
fix(fixer) {
|
1567
|
+
return fixer.replaceText(declaredDependenciesNode, "[".concat(suggestedDeps.map(element => formatDependency(element)).join(', '), "]"));
|
1568
|
+
}
|
1569
|
+
}]
|
1570
|
+
});
|
1571
|
+
}
|
1572
|
+
function visitCallExpression(node) {
|
1573
|
+
const callbackIndex = getReactiveHookCallbackIndex(node.callee, options);
|
1574
|
+
if (callbackIndex === -1) {
|
1575
|
+
return;
|
1576
|
+
}
|
1577
|
+
const callback = node.arguments[callbackIndex];
|
1578
|
+
const reactiveHook = node.callee;
|
1579
|
+
const reactiveHookName = getNodeWithoutReactNamespace(reactiveHook).name;
|
1580
|
+
const maybeNode = node.arguments[callbackIndex + 1];
|
1581
|
+
const declaredDependenciesNode = maybeNode && !(maybeNode.type === 'Identifier' && maybeNode.name === 'undefined') ? maybeNode : undefined;
|
1582
|
+
const isEffect = /Effect($|[^a-z])/g.test(reactiveHookName);
|
1583
|
+
if (!callback) {
|
1584
|
+
reportProblem({
|
1585
|
+
node: reactiveHook,
|
1586
|
+
message: "React Hook ".concat(reactiveHookName, " requires an effect callback. ") + "Did you forget to pass a callback to the hook?"
|
1587
|
+
});
|
1588
|
+
return;
|
1589
|
+
}
|
1590
|
+
if (!declaredDependenciesNode && !isEffect) {
|
1591
|
+
if (reactiveHookName === 'useMemo' || reactiveHookName === 'useCallback') {
|
1592
|
+
reportProblem({
|
1593
|
+
node: reactiveHook,
|
1594
|
+
message: "React Hook ".concat(reactiveHookName, " does nothing when called with ") + "only one argument. Did you forget to pass an array of " + "dependencies?"
|
1595
|
+
});
|
1596
|
+
}
|
1597
|
+
return;
|
1598
|
+
}
|
1599
|
+
switch (callback.type) {
|
1600
|
+
case 'FunctionExpression':
|
1601
|
+
case 'ArrowFunctionExpression':
|
1602
|
+
visitFunctionWithDependencies(callback, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
|
1603
|
+
return;
|
1604
|
+
case 'TSAsExpression':
|
1605
|
+
visitFunctionWithDependencies(callback.expression, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
|
1606
|
+
return;
|
1607
|
+
case 'Identifier':
|
1608
|
+
if (!declaredDependenciesNode) {
|
1609
|
+
return;
|
1610
|
+
}
|
1611
|
+
if (declaredDependenciesNode.elements && declaredDependenciesNode.elements.some(el => el && el.type === 'Identifier' && el.name === callback.name)) {
|
1612
|
+
return;
|
1613
|
+
}
|
1614
|
+
const variable = getScope(callback).set.get(callback.name);
|
1615
|
+
if (variable == null || variable.defs == null) {
|
1616
|
+
return;
|
1617
|
+
}
|
1618
|
+
const def = variable.defs[0];
|
1619
|
+
if (!def || !def.node) {
|
1620
|
+
break;
|
1621
|
+
}
|
1622
|
+
if (def.type !== 'Variable' && def.type !== 'FunctionName') {
|
1623
|
+
break;
|
1624
|
+
}
|
1625
|
+
switch (def.node.type) {
|
1626
|
+
case 'FunctionDeclaration':
|
1627
|
+
visitFunctionWithDependencies(def.node, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
|
1628
|
+
return;
|
1629
|
+
case 'VariableDeclarator':
|
1630
|
+
const init = def.node.init;
|
1631
|
+
if (!init) {
|
1632
|
+
break;
|
1633
|
+
}
|
1634
|
+
switch (init.type) {
|
1635
|
+
case 'ArrowFunctionExpression':
|
1636
|
+
case 'FunctionExpression':
|
1637
|
+
visitFunctionWithDependencies(init, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
|
1638
|
+
return;
|
1639
|
+
}
|
1640
|
+
break;
|
1641
|
+
}
|
1642
|
+
break;
|
1643
|
+
default:
|
1644
|
+
reportProblem({
|
1645
|
+
node: reactiveHook,
|
1646
|
+
message: "React Hook ".concat(reactiveHookName, " received a function whose dependencies ") + "are unknown. Pass an inline function instead."
|
1647
|
+
});
|
1648
|
+
return;
|
1649
|
+
}
|
1650
|
+
reportProblem({
|
1651
|
+
node: reactiveHook,
|
1652
|
+
message: "React Hook ".concat(reactiveHookName, " has a missing dependency: '").concat(callback.name, "'. ") + "Either include it or remove the dependency array.",
|
1653
|
+
suggest: [{
|
1654
|
+
desc: "Update the dependencies array to be: [".concat(callback.name, "]"),
|
1655
|
+
fix(fixer) {
|
1656
|
+
return fixer.replaceText(declaredDependenciesNode, "[".concat(callback.name, "]"));
|
1657
|
+
}
|
1658
|
+
}]
|
1659
|
+
});
|
1660
|
+
}
|
1661
|
+
return {
|
1662
|
+
CallExpression: visitCallExpression
|
1663
|
+
};
|
1664
|
+
}
|
1665
|
+
};
|
1666
|
+
function collectRecommendations({
|
1667
|
+
dependencies,
|
1668
|
+
declaredDependencies,
|
1669
|
+
stableDependencies,
|
1670
|
+
externalDependencies,
|
1671
|
+
isEffect
|
1672
|
+
}) {
|
1673
|
+
const depTree = createDepTree();
|
1674
|
+
function createDepTree() {
|
1675
|
+
return {
|
1676
|
+
isUsed: false,
|
1677
|
+
isSatisfiedRecursively: false,
|
1678
|
+
isSubtreeUsed: false,
|
1679
|
+
children: new Map()
|
1680
|
+
};
|
1681
|
+
}
|
1682
|
+
dependencies.forEach((_, key) => {
|
1683
|
+
const node = getOrCreateNodeByPath(depTree, key);
|
1684
|
+
node.isUsed = true;
|
1685
|
+
markAllParentsByPath(depTree, key, parent => {
|
1686
|
+
parent.isSubtreeUsed = true;
|
1687
|
+
});
|
1688
|
+
});
|
1689
|
+
declaredDependencies.forEach(({
|
1690
|
+
key
|
1691
|
+
}) => {
|
1692
|
+
const node = getOrCreateNodeByPath(depTree, key);
|
1693
|
+
node.isSatisfiedRecursively = true;
|
1694
|
+
});
|
1695
|
+
stableDependencies.forEach(key => {
|
1696
|
+
const node = getOrCreateNodeByPath(depTree, key);
|
1697
|
+
node.isSatisfiedRecursively = true;
|
1698
|
+
});
|
1699
|
+
function getOrCreateNodeByPath(rootNode, path) {
|
1700
|
+
const keys = path.split('.');
|
1701
|
+
let node = rootNode;
|
1702
|
+
for (const key of keys) {
|
1703
|
+
let child = node.children.get(key);
|
1704
|
+
if (!child) {
|
1705
|
+
child = createDepTree();
|
1706
|
+
node.children.set(key, child);
|
1707
|
+
}
|
1708
|
+
node = child;
|
1709
|
+
}
|
1710
|
+
return node;
|
1711
|
+
}
|
1712
|
+
function markAllParentsByPath(rootNode, path, fn) {
|
1713
|
+
const keys = path.split('.');
|
1714
|
+
let node = rootNode;
|
1715
|
+
for (const key of keys) {
|
1716
|
+
const child = node.children.get(key);
|
1717
|
+
if (!child) {
|
1718
|
+
return;
|
1719
|
+
}
|
1720
|
+
fn(child);
|
1721
|
+
node = child;
|
1722
|
+
}
|
1723
|
+
}
|
1724
|
+
const missingDependencies = new Set();
|
1725
|
+
const satisfyingDependencies = new Set();
|
1726
|
+
scanTreeRecursively(depTree, missingDependencies, satisfyingDependencies, key => key);
|
1727
|
+
function scanTreeRecursively(node, missingPaths, satisfyingPaths, keyToPath) {
|
1728
|
+
node.children.forEach((child, key) => {
|
1729
|
+
const path = keyToPath(key);
|
1730
|
+
if (child.isSatisfiedRecursively) {
|
1731
|
+
if (child.isSubtreeUsed) {
|
1732
|
+
satisfyingPaths.add(path);
|
1733
|
+
}
|
1734
|
+
return;
|
1735
|
+
}
|
1736
|
+
if (child.isUsed) {
|
1737
|
+
missingPaths.add(path);
|
1738
|
+
return;
|
1739
|
+
}
|
1740
|
+
scanTreeRecursively(child, missingPaths, satisfyingPaths, childKey => path + '.' + childKey);
|
1741
|
+
});
|
1742
|
+
}
|
1743
|
+
const suggestedDependencies = [];
|
1744
|
+
const unnecessaryDependencies = new Set();
|
1745
|
+
const duplicateDependencies = new Set();
|
1746
|
+
declaredDependencies.forEach(({
|
1747
|
+
key
|
1748
|
+
}) => {
|
1749
|
+
if (satisfyingDependencies.has(key)) {
|
1750
|
+
if (!suggestedDependencies.includes(key)) {
|
1751
|
+
suggestedDependencies.push(key);
|
1752
|
+
} else {
|
1753
|
+
duplicateDependencies.add(key);
|
1754
|
+
}
|
1755
|
+
} else if (isEffect && !key.endsWith('.current') && !externalDependencies.has(key)) {
|
1756
|
+
if (!suggestedDependencies.includes(key)) {
|
1757
|
+
suggestedDependencies.push(key);
|
1758
|
+
}
|
1759
|
+
} else {
|
1760
|
+
unnecessaryDependencies.add(key);
|
1761
|
+
}
|
1762
|
+
});
|
1763
|
+
missingDependencies.forEach(key => {
|
1764
|
+
suggestedDependencies.push(key);
|
1765
|
+
});
|
1766
|
+
return {
|
1767
|
+
suggestedDependencies,
|
1768
|
+
unnecessaryDependencies,
|
1769
|
+
duplicateDependencies,
|
1770
|
+
missingDependencies
|
1771
|
+
};
|
1772
|
+
}
|
1773
|
+
function getConstructionExpressionType(node) {
|
1774
|
+
switch (node.type) {
|
1775
|
+
case 'ObjectExpression':
|
1776
|
+
return 'object';
|
1777
|
+
case 'ArrayExpression':
|
1778
|
+
return 'array';
|
1779
|
+
case 'ArrowFunctionExpression':
|
1780
|
+
case 'FunctionExpression':
|
1781
|
+
return 'function';
|
1782
|
+
case 'ClassExpression':
|
1783
|
+
return 'class';
|
1784
|
+
case 'ConditionalExpression':
|
1785
|
+
if (getConstructionExpressionType(node.consequent) != null || getConstructionExpressionType(node.alternate) != null) {
|
1786
|
+
return 'conditional';
|
1787
|
+
}
|
1788
|
+
return null;
|
1789
|
+
case 'LogicalExpression':
|
1790
|
+
if (getConstructionExpressionType(node.left) != null || getConstructionExpressionType(node.right) != null) {
|
1791
|
+
return 'logical expression';
|
1792
|
+
}
|
1793
|
+
return null;
|
1794
|
+
case 'JSXFragment':
|
1795
|
+
return 'JSX fragment';
|
1796
|
+
case 'JSXElement':
|
1797
|
+
return 'JSX element';
|
1798
|
+
case 'AssignmentExpression':
|
1799
|
+
if (getConstructionExpressionType(node.right) != null) {
|
1800
|
+
return 'assignment expression';
|
1801
|
+
}
|
1802
|
+
return null;
|
1803
|
+
case 'NewExpression':
|
1804
|
+
return 'object construction';
|
1805
|
+
case 'Literal':
|
1806
|
+
if (node.value instanceof RegExp) {
|
1807
|
+
return 'regular expression';
|
1808
|
+
}
|
1809
|
+
return null;
|
1810
|
+
case 'TypeCastExpression':
|
1811
|
+
case 'AsExpression':
|
1812
|
+
case 'TSAsExpression':
|
1813
|
+
return getConstructionExpressionType(node.expression);
|
1814
|
+
}
|
1815
|
+
return null;
|
1816
|
+
}
|
1817
|
+
function scanForConstructions({
|
1818
|
+
declaredDependencies,
|
1819
|
+
declaredDependenciesNode,
|
1820
|
+
componentScope,
|
1821
|
+
scope
|
1822
|
+
}) {
|
1823
|
+
const constructions = declaredDependencies.map(({
|
1824
|
+
key
|
1825
|
+
}) => {
|
1826
|
+
const ref = componentScope.variables.find(v => v.name === key);
|
1827
|
+
if (ref == null) {
|
1828
|
+
return null;
|
1829
|
+
}
|
1830
|
+
const node = ref.defs[0];
|
1831
|
+
if (node == null) {
|
1832
|
+
return null;
|
1833
|
+
}
|
1834
|
+
if (node.type === 'Variable' && node.node.type === 'VariableDeclarator' && node.node.id.type === 'Identifier' &&
|
1835
|
+
node.node.init != null) {
|
1836
|
+
const constantExpressionType = getConstructionExpressionType(node.node.init);
|
1837
|
+
if (constantExpressionType != null) {
|
1838
|
+
return [ref, constantExpressionType];
|
1839
|
+
}
|
1840
|
+
}
|
1841
|
+
if (node.type === 'FunctionName' && node.node.type === 'FunctionDeclaration') {
|
1842
|
+
return [ref, 'function'];
|
1843
|
+
}
|
1844
|
+
if (node.type === 'ClassName' && node.node.type === 'ClassDeclaration') {
|
1845
|
+
return [ref, 'class'];
|
1846
|
+
}
|
1847
|
+
return null;
|
1848
|
+
}).filter(Boolean);
|
1849
|
+
function isUsedOutsideOfHook(ref) {
|
1850
|
+
let foundWriteExpr = false;
|
1851
|
+
for (let i = 0; i < ref.references.length; i++) {
|
1852
|
+
const reference = ref.references[i];
|
1853
|
+
if (reference.writeExpr) {
|
1854
|
+
if (foundWriteExpr) {
|
1855
|
+
return true;
|
1856
|
+
}
|
1857
|
+
foundWriteExpr = true;
|
1858
|
+
continue;
|
1859
|
+
}
|
1860
|
+
let currentScope = reference.from;
|
1861
|
+
while (currentScope !== scope && currentScope != null) {
|
1862
|
+
currentScope = currentScope.upper;
|
1863
|
+
}
|
1864
|
+
if (currentScope !== scope &&
|
1865
|
+
!isAncestorNodeOf(declaredDependenciesNode, reference.identifier)) {
|
1866
|
+
return true;
|
1867
|
+
}
|
1868
|
+
}
|
1869
|
+
return false;
|
1870
|
+
}
|
1871
|
+
return constructions.map(([ref, depType]) => ({
|
1872
|
+
construction: ref.defs[0],
|
1873
|
+
depType,
|
1874
|
+
isUsedOutsideOfHook: isUsedOutsideOfHook(ref)
|
1875
|
+
}));
|
1876
|
+
}
|
1877
|
+
function getDependency(node) {
|
1878
|
+
if ((node.parent.type === 'MemberExpression' || node.parent.type === 'OptionalMemberExpression') && node.parent.object === node && node.parent.property.name !== 'current' && !node.parent.computed && !(node.parent.parent != null && (node.parent.parent.type === 'CallExpression' || node.parent.parent.type === 'OptionalCallExpression') && node.parent.parent.callee === node.parent)) {
|
1879
|
+
return getDependency(node.parent);
|
1880
|
+
} else if (
|
1881
|
+
node.type === 'MemberExpression' && node.parent && node.parent.type === 'AssignmentExpression' && node.parent.left === node) {
|
1882
|
+
return node.object;
|
1883
|
+
}
|
1884
|
+
return node;
|
1885
|
+
}
|
1886
|
+
function markNode(node, optionalChains, result) {
|
1887
|
+
if (optionalChains) {
|
1888
|
+
if (node.optional) {
|
1889
|
+
if (!optionalChains.has(result)) {
|
1890
|
+
optionalChains.set(result, true);
|
1891
|
+
}
|
1892
|
+
} else {
|
1893
|
+
optionalChains.set(result, false);
|
1894
|
+
}
|
1895
|
+
}
|
1896
|
+
}
|
1897
|
+
function analyzePropertyChain(node, optionalChains) {
|
1898
|
+
if (node.type === 'Identifier' || node.type === 'JSXIdentifier') {
|
1899
|
+
const result = node.name;
|
1900
|
+
if (optionalChains) {
|
1901
|
+
optionalChains.set(result, false);
|
1902
|
+
}
|
1903
|
+
return result;
|
1904
|
+
} else if (node.type === 'MemberExpression' && !node.computed) {
|
1905
|
+
const object = analyzePropertyChain(node.object, optionalChains);
|
1906
|
+
const property = analyzePropertyChain(node.property, null);
|
1907
|
+
const result = "".concat(object, ".").concat(property);
|
1908
|
+
markNode(node, optionalChains, result);
|
1909
|
+
return result;
|
1910
|
+
} else if (node.type === 'OptionalMemberExpression' && !node.computed) {
|
1911
|
+
const object = analyzePropertyChain(node.object, optionalChains);
|
1912
|
+
const property = analyzePropertyChain(node.property, null);
|
1913
|
+
const result = "".concat(object, ".").concat(property);
|
1914
|
+
markNode(node, optionalChains, result);
|
1915
|
+
return result;
|
1916
|
+
} else if (node.type === 'ChainExpression' && !node.computed) {
|
1917
|
+
const expression = node.expression;
|
1918
|
+
if (expression.type === 'CallExpression') {
|
1919
|
+
throw new Error("Unsupported node type: ".concat(expression.type));
|
1920
|
+
}
|
1921
|
+
const object = analyzePropertyChain(expression.object, optionalChains);
|
1922
|
+
const property = analyzePropertyChain(expression.property, null);
|
1923
|
+
const result = "".concat(object, ".").concat(property);
|
1924
|
+
markNode(expression, optionalChains, result);
|
1925
|
+
return result;
|
1926
|
+
}
|
1927
|
+
throw new Error("Unsupported node type: ".concat(node.type));
|
1928
|
+
}
|
1929
|
+
function getNodeWithoutReactNamespace(node) {
|
1930
|
+
if (node.type === 'MemberExpression' && node.object.type === 'Identifier' && node.object.name === 'React' && node.property.type === 'Identifier' && !node.computed) {
|
1931
|
+
return node.property;
|
1932
|
+
}
|
1933
|
+
return node;
|
1934
|
+
}
|
1935
|
+
function getReactiveHookCallbackIndex(calleeNode, options) {
|
1936
|
+
const node = getNodeWithoutReactNamespace(calleeNode);
|
1937
|
+
if (node.type !== 'Identifier') {
|
1938
|
+
return -1;
|
1939
|
+
}
|
1940
|
+
switch (node.name) {
|
1941
|
+
case 'useEffect':
|
1942
|
+
case 'useLayoutEffect':
|
1943
|
+
case 'useCallback':
|
1944
|
+
case 'useMemo':
|
1945
|
+
return 0;
|
1946
|
+
case 'useImperativeHandle':
|
1947
|
+
return 1;
|
1948
|
+
default:
|
1949
|
+
if (node === calleeNode && options && options.customHooks) {
|
1950
|
+
let name;
|
1951
|
+
try {
|
1952
|
+
name = analyzePropertyChain(node, null);
|
1953
|
+
} catch (err) {
|
1954
|
+
if (/Unsupported node type/.test(err.message)) {
|
1955
|
+
return 0;
|
1956
|
+
}
|
1957
|
+
throw err;
|
1958
|
+
}
|
1959
|
+
for (const [key, customParts] of Object.entries(options.customHooks)) {
|
1960
|
+
if (typeof customParts === 'object' && typeof customParts.callbackIndex === 'number' && new RegExp(key).test(name)) {
|
1961
|
+
return customParts.callbackIndex;
|
1962
|
+
}
|
1963
|
+
}
|
1964
|
+
}
|
1965
|
+
if (node === calleeNode && options && options.additionalHooks) {
|
1966
|
+
let name;
|
1967
|
+
try {
|
1968
|
+
name = analyzePropertyChain(node, null);
|
1969
|
+
} catch (err) {
|
1970
|
+
if (/Unsupported node type/.test(err.message)) {
|
1971
|
+
return 0;
|
1972
|
+
}
|
1973
|
+
throw err;
|
1974
|
+
}
|
1975
|
+
return options.additionalHooks.test(name) ? 0 : -1;
|
1976
|
+
}
|
1977
|
+
return -1;
|
1978
|
+
}
|
1979
|
+
}
|
1980
|
+
function fastFindReferenceWithParent(start, target) {
|
1981
|
+
const queue = [start];
|
1982
|
+
let item = null;
|
1983
|
+
while (queue.length > 0) {
|
1984
|
+
item = queue.shift();
|
1985
|
+
if (isSameIdentifier(item, target)) {
|
1986
|
+
return item;
|
1987
|
+
}
|
1988
|
+
if (!isAncestorNodeOf(item, target)) {
|
1989
|
+
continue;
|
1990
|
+
}
|
1991
|
+
for (const [key, value] of Object.entries(item)) {
|
1992
|
+
if (key === 'parent') {
|
1993
|
+
continue;
|
1994
|
+
}
|
1995
|
+
if (isNodeLike(value)) {
|
1996
|
+
value.parent = item;
|
1997
|
+
queue.push(value);
|
1998
|
+
} else if (Array.isArray(value)) {
|
1999
|
+
value.forEach(val => {
|
2000
|
+
if (isNodeLike(val)) {
|
2001
|
+
val.parent = item;
|
2002
|
+
queue.push(val);
|
2003
|
+
}
|
2004
|
+
});
|
2005
|
+
}
|
2006
|
+
}
|
2007
|
+
}
|
2008
|
+
return null;
|
2009
|
+
}
|
2010
|
+
function joinEnglish(arr) {
|
2011
|
+
let s = '';
|
2012
|
+
for (let i = 0; i < arr.length; i++) {
|
2013
|
+
s += arr[i];
|
2014
|
+
if (i === 0 && arr.length === 2) {
|
2015
|
+
s += ' and ';
|
2016
|
+
} else if (i === arr.length - 2 && arr.length > 2) {
|
2017
|
+
s += ', and ';
|
2018
|
+
} else if (i < arr.length - 1) {
|
2019
|
+
s += ', ';
|
2020
|
+
}
|
2021
|
+
}
|
2022
|
+
return s;
|
2023
|
+
}
|
2024
|
+
function isNodeLike(val) {
|
2025
|
+
return typeof val === 'object' && val != null && !Array.isArray(val) && typeof val.type === 'string';
|
2026
|
+
}
|
2027
|
+
function isSameIdentifier(a, b) {
|
2028
|
+
return (a.type === 'Identifier' || a.type === 'JSXIdentifier') && a.type === b.type && a.name === b.name && a.range[0] === b.range[0] && a.range[1] === b.range[1];
|
2029
|
+
}
|
2030
|
+
function isAncestorNodeOf(a, b) {
|
2031
|
+
return a.range[0] <= b.range[0] && a.range[1] >= b.range[1];
|
2032
|
+
}
|
2033
|
+
function isUseEffectEventIdentifier(node) {
|
2034
|
+
return false;
|
2035
|
+
}
|
2036
|
+
|
2037
|
+
const RULE_NAME$5 = 'react-hook-use-ref';
|
2038
|
+
var reactHookUseRef = createRule({
|
2039
|
+
name: RULE_NAME$5,
|
2040
|
+
meta: {
|
2041
|
+
type: 'suggestion',
|
2042
|
+
docs: {
|
2043
|
+
description: 'Ensure naming of useRef hook value.',
|
2044
|
+
recommended: 'recommended'
|
2045
|
+
},
|
2046
|
+
schema: [],
|
2047
|
+
hasSuggestions: true,
|
2048
|
+
messages: {
|
2049
|
+
useRefName: 'useRef call is not end with "Ref"'
|
2050
|
+
}
|
2051
|
+
},
|
2052
|
+
defaultOptions: [],
|
2053
|
+
create: Components["default"].detect((context, component, util) => ({
|
2054
|
+
CallExpression(node) {
|
2055
|
+
const isImmediateReturn = node.parent && node.parent.type === AST_NODE_TYPES.ReturnStatement;
|
2056
|
+
if (isImmediateReturn || !util.isReactHookCall(node, ['useRef'])) {
|
2057
|
+
return;
|
2058
|
+
}
|
2059
|
+
if (node.parent.id.type !== AST_NODE_TYPES.Identifier) {
|
2060
|
+
return;
|
2061
|
+
}
|
2062
|
+
const variable = node.parent.id.name;
|
2063
|
+
if (!variable.endsWith('Ref')) {
|
2064
|
+
context.report({
|
2065
|
+
node: node,
|
2066
|
+
messageId: 'useRefName'
|
2067
|
+
});
|
2068
|
+
}
|
2069
|
+
}
|
2070
|
+
}))
|
2071
|
+
});
|
2072
|
+
|
2073
|
+
const RULE_NAME$4 = 'react-prefer-sx-prop';
|
2074
|
+
var reactPreferSxProp = createRule({
|
2075
|
+
name: RULE_NAME$4,
|
2076
|
+
meta: {
|
2077
|
+
type: 'problem',
|
2078
|
+
docs: {
|
2079
|
+
description: 'Prefer using sx prop instead of inline styles',
|
2080
|
+
recommended: 'recommended'
|
2081
|
+
},
|
2082
|
+
messages: {
|
2083
|
+
preferSxProp: 'Avoid using inline styles, use sx prop or tss-react or styled-component instead'
|
2084
|
+
},
|
2085
|
+
schema: [{
|
2086
|
+
type: 'object',
|
2087
|
+
properties: {
|
2088
|
+
allowedFor: {
|
2089
|
+
type: 'array',
|
2090
|
+
uniqueItems: true,
|
2091
|
+
items: {
|
2092
|
+
type: 'string'
|
2093
|
+
}
|
2094
|
+
}
|
2095
|
+
}
|
2096
|
+
}]
|
2097
|
+
},
|
2098
|
+
defaultOptions: [],
|
2099
|
+
create(context) {
|
2100
|
+
const configuration = context.options[0] || {};
|
2101
|
+
const allowedFor = configuration.allowedFor || [];
|
2102
|
+
function checkComponent(node) {
|
2103
|
+
const parentName = node.parent.name;
|
2104
|
+
const tag = parentName.name || "".concat(parentName.object.name, ".").concat(parentName.property.name);
|
2105
|
+
const componentName = parentName.name || parentName.property.name;
|
2106
|
+
if (componentName && typeof componentName[0] === 'string' && componentName[0] !== componentName[0].toUpperCase()) {
|
2107
|
+
return;
|
2108
|
+
}
|
2109
|
+
if (allowedFor.includes(tag)) {
|
2110
|
+
return;
|
2111
|
+
}
|
2112
|
+
const prop = node.name.name;
|
2113
|
+
if (prop === 'style') {
|
2114
|
+
context.report({
|
2115
|
+
node,
|
2116
|
+
messageId: 'preferSxProp'
|
2117
|
+
});
|
2118
|
+
}
|
2119
|
+
}
|
2120
|
+
function checkDOMNodes(node) {
|
2121
|
+
const tag = node.parent.name.name;
|
2122
|
+
if (!(tag && typeof tag === 'string' && tag[0] !== tag[0].toUpperCase())) {
|
2123
|
+
return;
|
2124
|
+
}
|
2125
|
+
if (allowedFor.includes(tag)) {
|
2126
|
+
return;
|
2127
|
+
}
|
2128
|
+
const prop = node.name.name;
|
2129
|
+
if (prop === 'style') {
|
2130
|
+
context.report({
|
2131
|
+
node,
|
2132
|
+
messageId: 'preferSxProp'
|
2133
|
+
});
|
2134
|
+
}
|
2135
|
+
}
|
2136
|
+
return {
|
2137
|
+
JSXAttribute(node) {
|
2138
|
+
checkComponent(node);
|
2139
|
+
checkDOMNodes(node);
|
2140
|
+
}
|
2141
|
+
};
|
2142
|
+
}
|
2143
|
+
});
|
2144
|
+
|
2145
|
+
function getBasicIdentifier(node) {
|
2146
|
+
if (node.type === AST_NODE_TYPES.Identifier) {
|
2147
|
+
return node.name;
|
2148
|
+
}
|
2149
|
+
if (node.type === AST_NODE_TYPES.Literal) {
|
2150
|
+
return node.value;
|
2151
|
+
}
|
2152
|
+
if (node.type === AST_NODE_TYPES.TemplateLiteral) {
|
2153
|
+
if (node.expressions.length > 0) {
|
2154
|
+
return null;
|
2155
|
+
}
|
2156
|
+
return node.quasis[0].value.raw;
|
2157
|
+
}
|
2158
|
+
return null;
|
2159
|
+
}
|
2160
|
+
function getBaseIdentifier(node) {
|
2161
|
+
switch (node.type) {
|
2162
|
+
case AST_NODE_TYPES.Identifier:
|
2163
|
+
{
|
2164
|
+
return node;
|
2165
|
+
}
|
2166
|
+
case AST_NODE_TYPES.CallExpression:
|
2167
|
+
{
|
2168
|
+
return getBaseIdentifier(node.callee);
|
2169
|
+
}
|
2170
|
+
case AST_NODE_TYPES.MemberExpression:
|
2171
|
+
{
|
2172
|
+
return getBaseIdentifier(node.object);
|
2173
|
+
}
|
2174
|
+
}
|
2175
|
+
return null;
|
2176
|
+
}
|
2177
|
+
function getStyesObj(node) {
|
2178
|
+
const isMakeStyles = node.callee.name === 'makeStyles';
|
2179
|
+
const isModernApi = node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.name === 'create' && getBaseIdentifier(node.callee.object) && getBaseIdentifier(node.callee.object).name === 'tss';
|
2180
|
+
if (!isMakeStyles && !isModernApi) {
|
2181
|
+
return;
|
2182
|
+
}
|
2183
|
+
const styles = (() => {
|
2184
|
+
if (isMakeStyles) {
|
2185
|
+
return node.parent.arguments[0];
|
2186
|
+
}
|
2187
|
+
if (isModernApi) {
|
2188
|
+
return node.callee.parent.arguments[0];
|
2189
|
+
}
|
2190
|
+
})();
|
2191
|
+
if (!styles) {
|
2192
|
+
return;
|
2193
|
+
}
|
2194
|
+
switch (styles.type) {
|
2195
|
+
case AST_NODE_TYPES.ObjectExpression:
|
2196
|
+
return styles;
|
2197
|
+
case AST_NODE_TYPES.ArrowFunctionExpression:
|
2198
|
+
{
|
2199
|
+
const {
|
2200
|
+
body
|
2201
|
+
} = styles;
|
2202
|
+
switch (body.type) {
|
2203
|
+
case AST_NODE_TYPES.ObjectExpression:
|
2204
|
+
return body;
|
2205
|
+
case AST_NODE_TYPES.BlockStatement:
|
2206
|
+
{
|
2207
|
+
let stylesObj;
|
2208
|
+
body.body.forEach(bodyNode => {
|
2209
|
+
if (bodyNode.type === AST_NODE_TYPES.ReturnStatement && bodyNode.argument.type === AST_NODE_TYPES.ObjectExpression) {
|
2210
|
+
stylesObj = bodyNode.argument;
|
2211
|
+
}
|
2212
|
+
});
|
2213
|
+
return stylesObj;
|
2214
|
+
}
|
2215
|
+
}
|
2216
|
+
}
|
2217
|
+
break;
|
2218
|
+
}
|
2219
|
+
}
|
2220
|
+
function loopStylesObj(node, callback) {
|
2221
|
+
if (node && node.type === AST_NODE_TYPES.ObjectExpression) {
|
2222
|
+
node.properties.forEach(property => {
|
2223
|
+
if (property.type === AST_NODE_TYPES.Property && property.value) {
|
2224
|
+
if (property.value.type === AST_NODE_TYPES.ObjectExpression) {
|
2225
|
+
loopStylesObj(property.value, callback);
|
2226
|
+
} else {
|
2227
|
+
callback(property.value);
|
2228
|
+
}
|
2229
|
+
}
|
2230
|
+
});
|
2231
|
+
}
|
2232
|
+
}
|
2233
|
+
|
2234
|
+
const RULE_NAME$3 = 'tss-class-naming';
|
2235
|
+
var tssClassNaming = createRule({
|
2236
|
+
name: RULE_NAME$3,
|
2237
|
+
meta: {
|
2238
|
+
type: 'problem',
|
2239
|
+
docs: {
|
2240
|
+
description: 'Enforce camelCase class names in TSS',
|
2241
|
+
recommended: 'recommended'
|
2242
|
+
},
|
2243
|
+
schema: [],
|
2244
|
+
messages: {
|
2245
|
+
camelCase: 'Class `{{ className }}` must be camelCase in TSS.'
|
2246
|
+
}
|
2247
|
+
},
|
2248
|
+
defaultOptions: [],
|
2249
|
+
create: function rule(context) {
|
2250
|
+
return {
|
2251
|
+
CallExpression(node) {
|
2252
|
+
const stylesObj = getStyesObj(node);
|
2253
|
+
if (stylesObj === undefined) {
|
2254
|
+
return;
|
2255
|
+
}
|
2256
|
+
stylesObj.properties.forEach(property => {
|
2257
|
+
if (property.computed) {
|
2258
|
+
return;
|
2259
|
+
}
|
2260
|
+
if (property.type === 'ExperimentalSpreadProperty' || property.type === AST_NODE_TYPES.SpreadElement) {
|
2261
|
+
return;
|
2262
|
+
}
|
2263
|
+
const className = property.key.value || property.key.name;
|
2264
|
+
if (!isCamelCase(className)) {
|
2265
|
+
context.report({
|
2266
|
+
node: property,
|
2267
|
+
messageId: 'camelCase',
|
2268
|
+
data: {
|
2269
|
+
className
|
2270
|
+
}
|
2271
|
+
});
|
2272
|
+
}
|
2273
|
+
});
|
2274
|
+
}
|
2275
|
+
};
|
2276
|
+
}
|
2277
|
+
});
|
2278
|
+
|
2279
|
+
var colors = {
|
2280
|
+
aliceblue: [240, 248, 255],
|
2281
|
+
antiquewhite: [250, 235, 215],
|
2282
|
+
aqua: [0, 255, 255],
|
2283
|
+
aquamarine: [127, 255, 212],
|
2284
|
+
azure: [240, 255, 255],
|
2285
|
+
beige: [245, 245, 220],
|
2286
|
+
bisque: [255, 228, 196],
|
2287
|
+
black: [0, 0, 0],
|
2288
|
+
blanchedalmond: [255, 235, 205],
|
2289
|
+
blue: [0, 0, 255],
|
2290
|
+
blueviolet: [138, 43, 226],
|
2291
|
+
brown: [165, 42, 42],
|
2292
|
+
burlywood: [222, 184, 135],
|
2293
|
+
cadetblue: [95, 158, 160],
|
2294
|
+
chartreuse: [127, 255, 0],
|
2295
|
+
chocolate: [210, 105, 30],
|
2296
|
+
coral: [255, 127, 80],
|
2297
|
+
cornflowerblue: [100, 149, 237],
|
2298
|
+
cornsilk: [255, 248, 220],
|
2299
|
+
crimson: [220, 20, 60],
|
2300
|
+
cyan: [0, 255, 255],
|
2301
|
+
darkblue: [0, 0, 139],
|
2302
|
+
darkcyan: [0, 139, 139],
|
2303
|
+
darkgoldenrod: [184, 134, 11],
|
2304
|
+
darkgray: [169, 169, 169],
|
2305
|
+
darkgreen: [0, 100, 0],
|
2306
|
+
darkgrey: [169, 169, 169],
|
2307
|
+
darkkhaki: [189, 183, 107],
|
2308
|
+
darkmagenta: [139, 0, 139],
|
2309
|
+
darkolivegreen: [85, 107, 47],
|
2310
|
+
darkorange: [255, 140, 0],
|
2311
|
+
darkorchid: [153, 50, 204],
|
2312
|
+
darkred: [139, 0, 0],
|
2313
|
+
darksalmon: [233, 150, 122],
|
2314
|
+
darkseagreen: [143, 188, 143],
|
2315
|
+
darkslateblue: [72, 61, 139],
|
2316
|
+
darkslategray: [47, 79, 79],
|
2317
|
+
darkslategrey: [47, 79, 79],
|
2318
|
+
darkturquoise: [0, 206, 209],
|
2319
|
+
darkviolet: [148, 0, 211],
|
2320
|
+
deeppink: [255, 20, 147],
|
2321
|
+
deepskyblue: [0, 191, 255],
|
2322
|
+
dimgray: [105, 105, 105],
|
2323
|
+
dimgrey: [105, 105, 105],
|
2324
|
+
dodgerblue: [30, 144, 255],
|
2325
|
+
firebrick: [178, 34, 34],
|
2326
|
+
floralwhite: [255, 250, 240],
|
2327
|
+
forestgreen: [34, 139, 34],
|
2328
|
+
fuchsia: [255, 0, 255],
|
2329
|
+
gainsboro: [220, 220, 220],
|
2330
|
+
ghostwhite: [248, 248, 255],
|
2331
|
+
gold: [255, 215, 0],
|
2332
|
+
goldenrod: [218, 165, 32],
|
2333
|
+
gray: [128, 128, 128],
|
2334
|
+
green: [0, 128, 0],
|
2335
|
+
greenyellow: [173, 255, 47],
|
2336
|
+
grey: [128, 128, 128],
|
2337
|
+
honeydew: [240, 255, 240],
|
2338
|
+
hotpink: [255, 105, 180],
|
2339
|
+
indianred: [205, 92, 92],
|
2340
|
+
indigo: [75, 0, 130],
|
2341
|
+
ivory: [255, 255, 240],
|
2342
|
+
khaki: [240, 230, 140],
|
2343
|
+
lavender: [230, 230, 250],
|
2344
|
+
lavenderblush: [255, 240, 245],
|
2345
|
+
lawngreen: [124, 252, 0],
|
2346
|
+
lemonchiffon: [255, 250, 205],
|
2347
|
+
lightblue: [173, 216, 230],
|
2348
|
+
lightcoral: [240, 128, 128],
|
2349
|
+
lightcyan: [224, 255, 255],
|
2350
|
+
lightgoldenrodyellow: [250, 250, 210],
|
2351
|
+
lightgray: [211, 211, 211],
|
2352
|
+
lightgreen: [144, 238, 144],
|
2353
|
+
lightgrey: [211, 211, 211],
|
2354
|
+
lightpink: [255, 182, 193],
|
2355
|
+
lightsalmon: [255, 160, 122],
|
2356
|
+
lightseagreen: [32, 178, 170],
|
2357
|
+
lightskyblue: [135, 206, 250],
|
2358
|
+
lightslategray: [119, 136, 153],
|
2359
|
+
lightslategrey: [119, 136, 153],
|
2360
|
+
lightsteelblue: [176, 196, 222],
|
2361
|
+
lightyellow: [255, 255, 224],
|
2362
|
+
lime: [0, 255, 0],
|
2363
|
+
limegreen: [50, 205, 50],
|
2364
|
+
linen: [250, 240, 230],
|
2365
|
+
magenta: [255, 0, 255],
|
2366
|
+
maroon: [128, 0, 0],
|
2367
|
+
mediumaquamarine: [102, 205, 170],
|
2368
|
+
mediumblue: [0, 0, 205],
|
2369
|
+
mediumorchid: [186, 85, 211],
|
2370
|
+
mediumpurple: [147, 112, 219],
|
2371
|
+
mediumseagreen: [60, 179, 113],
|
2372
|
+
mediumslateblue: [123, 104, 238],
|
2373
|
+
mediumspringgreen: [0, 250, 154],
|
2374
|
+
mediumturquoise: [72, 209, 204],
|
2375
|
+
mediumvioletred: [199, 21, 133],
|
2376
|
+
midnightblue: [25, 25, 112],
|
2377
|
+
mintcream: [245, 255, 250],
|
2378
|
+
mistyrose: [255, 228, 225],
|
2379
|
+
moccasin: [255, 228, 181],
|
2380
|
+
navajowhite: [255, 222, 173],
|
2381
|
+
navy: [0, 0, 128],
|
2382
|
+
oldlace: [253, 245, 230],
|
2383
|
+
olive: [128, 128, 0],
|
2384
|
+
olivedrab: [107, 142, 35],
|
2385
|
+
orange: [255, 165, 0],
|
2386
|
+
orangered: [255, 69, 0],
|
2387
|
+
orchid: [218, 112, 214],
|
2388
|
+
palegoldenrod: [238, 232, 170],
|
2389
|
+
palegreen: [152, 251, 152],
|
2390
|
+
paleturquoise: [175, 238, 238],
|
2391
|
+
palevioletred: [219, 112, 147],
|
2392
|
+
papayawhip: [255, 239, 213],
|
2393
|
+
peachpuff: [255, 218, 185],
|
2394
|
+
peru: [205, 133, 63],
|
2395
|
+
pink: [255, 192, 203],
|
2396
|
+
plum: [221, 160, 221],
|
2397
|
+
powderblue: [176, 224, 230],
|
2398
|
+
purple: [128, 0, 128],
|
2399
|
+
rebeccapurple: [102, 51, 153],
|
2400
|
+
red: [255, 0, 0],
|
2401
|
+
rosybrown: [188, 143, 143],
|
2402
|
+
royalblue: [65, 105, 225],
|
2403
|
+
saddlebrown: [139, 69, 19],
|
2404
|
+
salmon: [250, 128, 114],
|
2405
|
+
sandybrown: [244, 164, 96],
|
2406
|
+
seagreen: [46, 139, 87],
|
2407
|
+
seashell: [255, 245, 238],
|
2408
|
+
sienna: [160, 82, 45],
|
2409
|
+
silver: [192, 192, 192],
|
2410
|
+
skyblue: [135, 206, 235],
|
2411
|
+
slateblue: [106, 90, 205],
|
2412
|
+
slategray: [112, 128, 144],
|
2413
|
+
slategrey: [112, 128, 144],
|
2414
|
+
snow: [255, 250, 250],
|
2415
|
+
springgreen: [0, 255, 127],
|
2416
|
+
steelblue: [70, 130, 180],
|
2417
|
+
tan: [210, 180, 140],
|
2418
|
+
teal: [0, 128, 128],
|
2419
|
+
thistle: [216, 191, 216],
|
2420
|
+
tomato: [255, 99, 71],
|
2421
|
+
turquoise: [64, 224, 208],
|
2422
|
+
violet: [238, 130, 238],
|
2423
|
+
wheat: [245, 222, 179],
|
2424
|
+
white: [255, 255, 255],
|
2425
|
+
whitesmoke: [245, 245, 245],
|
2426
|
+
yellow: [255, 255, 0],
|
2427
|
+
yellowgreen: [154, 205, 50]
|
2428
|
+
};
|
2429
|
+
|
2430
|
+
const RULE_NAME$2 = 'tss-no-color-name';
|
2431
|
+
var tssNoColorName = createRule({
|
2432
|
+
name: RULE_NAME$2,
|
2433
|
+
meta: {
|
2434
|
+
type: 'suggestion',
|
2435
|
+
docs: {
|
2436
|
+
description: 'Enforce the use of color variables instead of color name within TSS',
|
2437
|
+
recommended: 'recommended'
|
2438
|
+
},
|
2439
|
+
schema: [],
|
2440
|
+
messages: {
|
2441
|
+
disallowColorName: 'Disallowed color name. Use color from `@mui/material/colors` or `theme.palette`.'
|
2442
|
+
}
|
2443
|
+
},
|
2444
|
+
defaultOptions: [],
|
2445
|
+
create: function (context) {
|
2446
|
+
return {
|
2447
|
+
CallExpression(node) {
|
2448
|
+
const stylesObj = getStyesObj(node);
|
2449
|
+
if (!stylesObj) {
|
2450
|
+
return;
|
2451
|
+
}
|
2452
|
+
function checkColorLiteral(value) {
|
2453
|
+
if (value.type === AST_NODE_TYPES.Literal && typeof value.value === 'string' && Object.keys(colors).includes(value.value.toLowerCase())) {
|
2454
|
+
context.report({
|
2455
|
+
node: value,
|
2456
|
+
messageId: 'disallowColorName'
|
2457
|
+
});
|
2458
|
+
}
|
2459
|
+
}
|
2460
|
+
loopStylesObj(stylesObj, checkColorLiteral);
|
2461
|
+
}
|
2462
|
+
};
|
2463
|
+
}
|
2464
|
+
});
|
2465
|
+
|
2466
|
+
const RULE_NAME$1 = 'tss-no-color-value';
|
2467
|
+
var tssNoColorValue = createRule({
|
2468
|
+
name: RULE_NAME$1,
|
2469
|
+
meta: {
|
2470
|
+
type: 'problem',
|
2471
|
+
docs: {
|
2472
|
+
description: 'Enforce the use of color variables instead of color codes within TSS',
|
2473
|
+
recommended: 'recommended'
|
2474
|
+
},
|
2475
|
+
schema: [],
|
2476
|
+
messages: {
|
2477
|
+
preferMuiColor: 'Prefer use color from `@mui/material/colors` or `theme.palette`.'
|
2478
|
+
}
|
2479
|
+
},
|
2480
|
+
defaultOptions: [],
|
2481
|
+
create: function (context) {
|
2482
|
+
return {
|
2483
|
+
CallExpression(node) {
|
2484
|
+
const stylesObj = getStyesObj(node);
|
2485
|
+
if (!stylesObj) {
|
2486
|
+
return;
|
2487
|
+
}
|
2488
|
+
function checkColorLiteral(value) {
|
2489
|
+
if (value.type === AST_NODE_TYPES.Literal && typeof value.value === 'string') {
|
2490
|
+
const colorCodePattern = /#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|rgb\?\(\s*(\d{1,3}\s*,\s*){2}\d{1,3}(?:\s*,\s*\d*(?:\.\d+)?)?\s*\)/g;
|
2491
|
+
if (colorCodePattern.test(value.value)) {
|
2492
|
+
context.report({
|
2493
|
+
node: value,
|
2494
|
+
messageId: 'preferMuiColor'
|
2495
|
+
});
|
2496
|
+
}
|
2497
|
+
}
|
2498
|
+
}
|
2499
|
+
loopStylesObj(stylesObj, checkColorLiteral);
|
2500
|
+
}
|
2501
|
+
};
|
2502
|
+
}
|
2503
|
+
});
|
2504
|
+
|
2505
|
+
const RULE_NAME = 'tss-unused-classes';
|
2506
|
+
var tssUnusedClasses = createRule({
|
2507
|
+
name: RULE_NAME,
|
2508
|
+
meta: {
|
2509
|
+
type: 'suggestion',
|
2510
|
+
docs: {
|
2511
|
+
description: 'Disallow unused classes in tss',
|
2512
|
+
recommended: 'recommended'
|
2513
|
+
},
|
2514
|
+
schema: [],
|
2515
|
+
messages: {
|
2516
|
+
unusedClass: 'Class `{{ className }}` is unused in JSX'
|
2517
|
+
}
|
2518
|
+
},
|
2519
|
+
defaultOptions: [],
|
2520
|
+
create: function rule(context) {
|
2521
|
+
const usedClasses = {};
|
2522
|
+
const definedClasses = {};
|
2523
|
+
return {
|
2524
|
+
CallExpression(node) {
|
2525
|
+
const stylesObj = getStyesObj(node);
|
2526
|
+
if (stylesObj === undefined) {
|
2527
|
+
return;
|
2528
|
+
}
|
2529
|
+
stylesObj.properties.forEach(property => {
|
2530
|
+
if (property.computed) {
|
2531
|
+
return;
|
2532
|
+
}
|
2533
|
+
if (property.type === 'ExperimentalSpreadProperty' || property.type === AST_NODE_TYPES.SpreadElement) {
|
2534
|
+
return;
|
2535
|
+
}
|
2536
|
+
definedClasses[property.key.value || property.key.name] = property;
|
2537
|
+
});
|
2538
|
+
},
|
2539
|
+
MemberExpression(node) {
|
2540
|
+
if (node.object.type === AST_NODE_TYPES.Identifier && node.object.name === 'classes') {
|
2541
|
+
const whichClass = getBasicIdentifier(node.property);
|
2542
|
+
if (whichClass) {
|
2543
|
+
usedClasses[whichClass] = true;
|
2544
|
+
}
|
2545
|
+
return;
|
2546
|
+
}
|
2547
|
+
const classIdentifier = getBasicIdentifier(node.property);
|
2548
|
+
if (!classIdentifier) {
|
2549
|
+
return;
|
2550
|
+
}
|
2551
|
+
if (classIdentifier !== 'classes') {
|
2552
|
+
return;
|
2553
|
+
}
|
2554
|
+
const {
|
2555
|
+
parent
|
2556
|
+
} = node;
|
2557
|
+
if (parent.type !== AST_NODE_TYPES.MemberExpression) {
|
2558
|
+
return;
|
2559
|
+
}
|
2560
|
+
if (
|
2561
|
+
node.object.object &&
|
2562
|
+
node.object.object.type !== AST_NODE_TYPES.ThisExpression) {
|
2563
|
+
return;
|
2564
|
+
}
|
2565
|
+
const propsIdentifier = getBasicIdentifier(parent.object);
|
2566
|
+
if (propsIdentifier && propsIdentifier !== 'props') {
|
2567
|
+
return;
|
2568
|
+
}
|
2569
|
+
if (!propsIdentifier && parent.object.type !== AST_NODE_TYPES.MemberExpression) {
|
2570
|
+
return;
|
2571
|
+
}
|
2572
|
+
if (parent.parent.type === AST_NODE_TYPES.MemberExpression) {
|
2573
|
+
return;
|
2574
|
+
}
|
2575
|
+
const parentClassIdentifier = getBasicIdentifier(parent.property);
|
2576
|
+
if (parentClassIdentifier) {
|
2577
|
+
usedClasses[parentClassIdentifier] = true;
|
2578
|
+
}
|
2579
|
+
},
|
2580
|
+
'Program:exit': () => {
|
2581
|
+
Object.keys(definedClasses).forEach(definedClassKey => {
|
2582
|
+
if (!usedClasses[definedClassKey]) {
|
2583
|
+
context.report({
|
2584
|
+
node: definedClasses[definedClassKey],
|
2585
|
+
messageId: 'unusedClass',
|
2586
|
+
data: {
|
2587
|
+
className: definedClassKey
|
2588
|
+
}
|
2589
|
+
});
|
2590
|
+
}
|
2591
|
+
});
|
2592
|
+
}
|
2593
|
+
};
|
2594
|
+
}
|
2595
|
+
});
|
2596
|
+
|
2597
|
+
var ruleFiles = /*#__PURE__*/Object.freeze({
|
2598
|
+
__proto__: null,
|
2599
|
+
rules_enforce_mui_icon_alias: enforceMuiIconAlias,
|
2600
|
+
rules_import_monorepo: importMonorepo,
|
2601
|
+
rules_intl_id_missing: intlIdMissing,
|
2602
|
+
rules_intl_id_naming: intlIdNaming,
|
2603
|
+
rules_intl_id_prefix: intlIdPrefix,
|
2604
|
+
rules_intl_id_unused: intlIdUnused,
|
2605
|
+
rules_intl_no_default: intlNoDefault,
|
2606
|
+
rules_no_async_array_methods: noAsyncArrayMethods,
|
2607
|
+
rules_no_extends_error: noExtendsError,
|
2608
|
+
rules_no_import_css: noImportCss,
|
2609
|
+
rules_no_then_catch_finally: noThenCatchFinally,
|
2610
|
+
rules_no_unnecessary_template_literals: noUnnecessaryTemplateLiterals,
|
2611
|
+
rules_react_better_exhaustive_deps: reactBetterExhaustiveDeps,
|
2612
|
+
rules_react_hook_use_ref: reactHookUseRef,
|
2613
|
+
rules_react_prefer_sx_prop: reactPreferSxProp,
|
2614
|
+
rules_tss_class_naming: tssClassNaming,
|
2615
|
+
rules_tss_no_color_name: tssNoColorName,
|
2616
|
+
rules_tss_no_color_value: tssNoColorValue,
|
2617
|
+
rules_tss_unused_classes: tssUnusedClasses
|
2618
|
+
});
|
2619
|
+
|
2620
|
+
const plugin = {
|
2621
|
+
rules: {},
|
2622
|
+
configs: {
|
2623
|
+
recommended: {
|
2624
|
+
plugins: ['@agilebot'],
|
2625
|
+
rules: {},
|
2626
|
+
settings: {
|
2627
|
+
react: {
|
2628
|
+
version: '18.0.0'
|
2629
|
+
}
|
2630
|
+
}
|
2631
|
+
}
|
2632
|
+
}
|
2633
|
+
};
|
2634
|
+
Object.keys(ruleFiles).forEach(key => {
|
2635
|
+
const ruleKey = key.replace(/^rules_/, '');
|
2636
|
+
const finalKey = ruleKey.replace(/_/g, '-');
|
2637
|
+
const rule = ruleFiles[key];
|
2638
|
+
plugin.rules[finalKey] = rule;
|
2639
|
+
if (rule.meta && rule.meta.docs && rule.meta.docs.recommended) {
|
2640
|
+
plugin.configs.recommended.rules["@agilebot/".concat(finalKey)] = rule.meta.type === 'problem' ? 'error' : 'warn';
|
2641
|
+
}
|
2642
|
+
});
|
2643
|
+
|
2644
|
+
export { plugin as default };
|