@blumintinc/eslint-plugin-blumint 1.7.3 → 1.8.1
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/README.md +83 -61
- package/lib/index.js +85 -1
- package/lib/rules/enforce-assert-throws.js +48 -3
- package/lib/rules/enforce-assertSafe-object-key.d.ts +2 -0
- package/lib/rules/enforce-assertSafe-object-key.js +284 -0
- package/lib/rules/enforce-centralized-mock-firestore.js +192 -14
- package/lib/rules/enforce-css-media-queries.d.ts +5 -0
- package/lib/rules/enforce-css-media-queries.js +87 -0
- package/lib/rules/enforce-dynamic-imports.d.ts +9 -0
- package/lib/rules/enforce-dynamic-imports.js +84 -0
- package/lib/rules/enforce-firestore-facade.js +8 -0
- package/lib/rules/enforce-id-capitalization.d.ts +6 -0
- package/lib/rules/enforce-id-capitalization.js +78 -0
- package/lib/rules/enforce-microdiff.d.ts +3 -0
- package/lib/rules/enforce-microdiff.js +379 -0
- package/lib/rules/enforce-mui-rounded-icons.d.ts +1 -0
- package/lib/rules/enforce-mui-rounded-icons.js +54 -0
- package/lib/rules/enforce-object-literal-as-const.d.ts +4 -0
- package/lib/rules/enforce-object-literal-as-const.js +88 -0
- package/lib/rules/enforce-positive-naming.d.ts +1 -0
- package/lib/rules/enforce-positive-naming.js +387 -0
- package/lib/rules/enforce-props-argument-name.d.ts +8 -0
- package/lib/rules/enforce-props-argument-name.js +182 -0
- package/lib/rules/enforce-react-type-naming.d.ts +3 -0
- package/lib/rules/enforce-react-type-naming.js +191 -0
- package/lib/rules/enforce-render-hits-memoization.js +155 -26
- package/lib/rules/enforce-singular-type-names.d.ts +2 -0
- package/lib/rules/enforce-singular-type-names.js +112 -0
- package/lib/rules/enforce-timestamp-now.d.ts +1 -0
- package/lib/rules/enforce-timestamp-now.js +223 -0
- package/lib/rules/enforce-verb-noun-naming.js +5 -2
- package/lib/rules/ensure-pointer-events-none.d.ts +1 -0
- package/lib/rules/ensure-pointer-events-none.js +211 -0
- package/lib/rules/extract-global-constants.d.ts +1 -1
- package/lib/rules/extract-global-constants.js +109 -1
- package/lib/rules/global-const-style.js +3 -2
- package/lib/rules/key-only-outermost-element.d.ts +3 -0
- package/lib/rules/key-only-outermost-element.js +152 -0
- package/lib/rules/no-always-true-false-conditions.d.ts +3 -0
- package/lib/rules/no-always-true-false-conditions.js +1221 -0
- package/lib/rules/no-circular-references.d.ts +1 -0
- package/lib/rules/no-circular-references.js +523 -0
- package/lib/rules/no-firestore-jest-mock.js +27 -4
- package/lib/rules/no-hungarian.d.ts +5 -0
- package/lib/rules/no-hungarian.js +223 -0
- package/lib/rules/no-mock-firebase-admin.js +20 -7
- package/lib/rules/no-object-values-on-strings.d.ts +2 -0
- package/lib/rules/no-object-values-on-strings.js +294 -0
- package/lib/rules/no-type-assertion-returns.d.ts +9 -0
- package/lib/rules/no-type-assertion-returns.js +299 -0
- package/lib/rules/no-unnecessary-destructuring.d.ts +5 -0
- package/lib/rules/no-unnecessary-destructuring.js +69 -0
- package/lib/rules/no-unnecessary-verb-suffix.d.ts +1 -0
- package/lib/rules/no-unnecessary-verb-suffix.js +203 -0
- package/lib/rules/no-unused-props.js +10 -5
- package/lib/rules/no-unused-usestate.d.ts +8 -0
- package/lib/rules/no-unused-usestate.js +84 -0
- package/lib/rules/omit-index-html.d.ts +7 -0
- package/lib/rules/omit-index-html.js +91 -0
- package/lib/rules/prefer-clone-deep.js +294 -22
- package/lib/rules/prefer-fragment-component.js +265 -34
- package/lib/rules/prefer-global-router-state-key.d.ts +5 -0
- package/lib/rules/prefer-global-router-state-key.js +89 -0
- package/lib/rules/prefer-usememo-over-useeffect-usestate.d.ts +5 -0
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +129 -0
- package/lib/rules/prefer-utility-function-over-private-static.d.ts +1 -0
- package/lib/rules/prefer-utility-function-over-private-static.js +77 -0
- package/lib/rules/react-usememo-should-be-component.d.ts +1 -0
- package/lib/rules/react-usememo-should-be-component.js +623 -0
- package/package.json +10 -7
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RULE_NAME = void 0;
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
exports.RULE_NAME = 'enforce-dynamic-imports';
|
|
6
|
+
exports.default = (0, createRule_1.createRule)({
|
|
7
|
+
name: exports.RULE_NAME,
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'suggestion',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Enforce dynamic imports for specified libraries to optimize bundle size',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
schema: [
|
|
15
|
+
{
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
libraries: {
|
|
19
|
+
type: 'array',
|
|
20
|
+
items: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
allowImportType: {
|
|
25
|
+
type: 'boolean',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
additionalProperties: false,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
messages: {
|
|
32
|
+
dynamicImportRequired: 'Static imports from "{{source}}" are not allowed to reduce initial bundle size. Use dynamic imports instead: const Component = useDynamic(() => import("{{source}}").then(mod => mod.Component))',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultOptions: [
|
|
36
|
+
{
|
|
37
|
+
libraries: ['@stream-io/video-react-sdk'],
|
|
38
|
+
allowImportType: true,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
create(context, [options]) {
|
|
42
|
+
const { libraries, allowImportType = true } = options;
|
|
43
|
+
// Check if the import source matches any of the specified libraries
|
|
44
|
+
const isLibraryMatch = (source) => {
|
|
45
|
+
return libraries.some((lib) => {
|
|
46
|
+
// Simple glob pattern matching
|
|
47
|
+
if (lib.includes('*')) {
|
|
48
|
+
const pattern = lib.replace(/\*/g, '.*');
|
|
49
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
50
|
+
return regex.test(source);
|
|
51
|
+
}
|
|
52
|
+
return source === lib;
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
ImportDeclaration(node) {
|
|
57
|
+
const importSource = node.source.value;
|
|
58
|
+
// Skip type-only imports if allowed
|
|
59
|
+
if (allowImportType) {
|
|
60
|
+
// Check if it's a type-only import declaration
|
|
61
|
+
if (node.importKind === 'type') {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Check if all specifiers are type imports
|
|
65
|
+
if (node.specifiers.length > 0 &&
|
|
66
|
+
node.specifiers.every(spec => (spec.type === 'ImportSpecifier' && spec.importKind === 'type'))) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Check if the import is from a library that should be dynamically imported
|
|
71
|
+
if (typeof importSource === 'string' && isLibraryMatch(importSource)) {
|
|
72
|
+
context.report({
|
|
73
|
+
node,
|
|
74
|
+
messageId: 'dynamicImportRequired',
|
|
75
|
+
data: {
|
|
76
|
+
source: importSource,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
//# sourceMappingURL=enforce-dynamic-imports.js.map
|
|
@@ -27,6 +27,10 @@ const isFirestoreMethodCall = (node) => {
|
|
|
27
27
|
return true;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
// Handle type assertions (as in the bug report)
|
|
31
|
+
if (object.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
30
34
|
// Check if it's a Firestore reference
|
|
31
35
|
let current = object;
|
|
32
36
|
let foundDocOrCollection = false;
|
|
@@ -44,6 +48,10 @@ const isFirestoreMethodCall = (node) => {
|
|
|
44
48
|
if (isMemberExpression(current)) {
|
|
45
49
|
current = current.object;
|
|
46
50
|
}
|
|
51
|
+
else if (current.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
|
|
52
|
+
// Handle nested type assertions
|
|
53
|
+
current = current.expression;
|
|
54
|
+
}
|
|
47
55
|
else {
|
|
48
56
|
break;
|
|
49
57
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This rule ensures consistency in user-facing text by enforcing the use of "ID"
|
|
3
|
+
* instead of "id" when referring to identifiers in UI labels, instructions,
|
|
4
|
+
* error messages, and other visible strings.
|
|
5
|
+
*/
|
|
6
|
+
export declare const enforceIdCapitalization: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"enforceIdCapitalization", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enforceIdCapitalization = void 0;
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
/**
|
|
6
|
+
* This rule ensures consistency in user-facing text by enforcing the use of "ID"
|
|
7
|
+
* instead of "id" when referring to identifiers in UI labels, instructions,
|
|
8
|
+
* error messages, and other visible strings.
|
|
9
|
+
*/
|
|
10
|
+
exports.enforceIdCapitalization = (0, createRule_1.createRule)({
|
|
11
|
+
name: 'enforce-id-capitalization',
|
|
12
|
+
meta: {
|
|
13
|
+
type: 'suggestion',
|
|
14
|
+
docs: {
|
|
15
|
+
description: 'Enforce the use of "ID" instead of "id" in user-facing text',
|
|
16
|
+
recommended: 'error',
|
|
17
|
+
},
|
|
18
|
+
fixable: 'code',
|
|
19
|
+
schema: [],
|
|
20
|
+
messages: {
|
|
21
|
+
enforceIdCapitalization: 'Use "ID" instead of "id" in user-facing text for better readability',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultOptions: [],
|
|
25
|
+
create(context) {
|
|
26
|
+
// Regular expression to match standalone "id" surrounded by whitespace or punctuation
|
|
27
|
+
// This ensures we only match "id" as a word, not as part of another word
|
|
28
|
+
const idRegex = /(^|\s|[.,;:!?'"()\[\]{}])id(\s|$|[.,;:!?'"()\[\]{}])/g;
|
|
29
|
+
/**
|
|
30
|
+
* Check if a string contains "id" as a standalone word and report if found
|
|
31
|
+
*/
|
|
32
|
+
function checkForIdInString(node, value) {
|
|
33
|
+
if (typeof value !== 'string')
|
|
34
|
+
return;
|
|
35
|
+
// Reset the regex lastIndex to ensure consistent behavior
|
|
36
|
+
idRegex.lastIndex = 0;
|
|
37
|
+
// Check if the string contains "id" as a standalone word
|
|
38
|
+
if (idRegex.test(value)) {
|
|
39
|
+
// Reset the regex lastIndex again before replacing
|
|
40
|
+
idRegex.lastIndex = 0;
|
|
41
|
+
const fixedText = value.replace(idRegex, (_match, prefix, suffix) => {
|
|
42
|
+
return `${prefix}ID${suffix}`;
|
|
43
|
+
});
|
|
44
|
+
context.report({
|
|
45
|
+
node,
|
|
46
|
+
messageId: 'enforceIdCapitalization',
|
|
47
|
+
fix: (fixer) => {
|
|
48
|
+
if (node.type === 'Literal') {
|
|
49
|
+
return fixer.replaceText(node, JSON.stringify(fixedText));
|
|
50
|
+
}
|
|
51
|
+
else if (node.type === 'JSXText') {
|
|
52
|
+
return fixer.replaceText(node, fixedText);
|
|
53
|
+
}
|
|
54
|
+
else if (node.type === 'TemplateElement') {
|
|
55
|
+
return fixer.replaceText(node, fixedText);
|
|
56
|
+
}
|
|
57
|
+
return fixer.replaceText(node, JSON.stringify(fixedText));
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
// Check string literals
|
|
64
|
+
Literal(node) {
|
|
65
|
+
if (typeof node.value === 'string') {
|
|
66
|
+
checkForIdInString(node, node.value);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
// Check JSX text
|
|
70
|
+
JSXText(node) {
|
|
71
|
+
checkForIdInString(node, node.value);
|
|
72
|
+
},
|
|
73
|
+
// We don't need a separate handler for CallExpression since we already handle Literals
|
|
74
|
+
// The Literal handler will catch the string arguments in t("user.profile.id")
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
//# sourceMappingURL=enforce-id-capitalization.js.map
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enforceMicrodiff = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
exports.enforceMicrodiff = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'enforce-microdiff',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'suggestion',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Enforce using microdiff for object and array comparison operations',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
enforceMicrodiff: 'Use the microdiff library for object and array comparison operations',
|
|
18
|
+
enforceMicrodiffImport: 'Import diff from microdiff instead of {{importSource}}',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultOptions: [],
|
|
22
|
+
create(context) {
|
|
23
|
+
const sourceCode = context.getSourceCode();
|
|
24
|
+
const importedDiffLibraries = new Map();
|
|
25
|
+
const importedFunctions = new Map(); // Map of imported function names to their sources
|
|
26
|
+
let hasMicrodiffImport = false;
|
|
27
|
+
const reportedNodes = new Set();
|
|
28
|
+
// Add a specific set to track which import names are used
|
|
29
|
+
const usedImportNames = new Set();
|
|
30
|
+
// Check if a node is an object or array type
|
|
31
|
+
function isObjectOrArrayType(node) {
|
|
32
|
+
if (node.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
|
|
33
|
+
node.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
// For identifiers, we'll make a simple assumption based on naming conventions
|
|
37
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
38
|
+
const name = node.name.toLowerCase();
|
|
39
|
+
// Names that likely represent objects or arrays
|
|
40
|
+
if (name.includes('obj') ||
|
|
41
|
+
name.includes('config') ||
|
|
42
|
+
name.includes('options') ||
|
|
43
|
+
name.includes('data') ||
|
|
44
|
+
name.includes('state') ||
|
|
45
|
+
name.includes('props') ||
|
|
46
|
+
name.includes('items') ||
|
|
47
|
+
name.includes('array') ||
|
|
48
|
+
name.includes('list') ||
|
|
49
|
+
name.endsWith('s')) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// For member expressions, assume they could be objects/arrays
|
|
54
|
+
if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
// Track imports of diffing libraries
|
|
61
|
+
ImportDeclaration(node) {
|
|
62
|
+
const importSource = node.source.value;
|
|
63
|
+
// Check for microdiff import
|
|
64
|
+
if (importSource === 'microdiff') {
|
|
65
|
+
hasMicrodiffImport = true;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Track other diffing libraries
|
|
69
|
+
if ([
|
|
70
|
+
'deep-diff',
|
|
71
|
+
'fast-diff',
|
|
72
|
+
'diff',
|
|
73
|
+
'deep-object-diff',
|
|
74
|
+
'fast-deep-equal',
|
|
75
|
+
'fast-deep-equal/es6',
|
|
76
|
+
].includes(importSource)) {
|
|
77
|
+
// Track imported function names and their sources
|
|
78
|
+
node.specifiers.forEach((specifier) => {
|
|
79
|
+
if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
80
|
+
(specifier.imported.name === 'diff' ||
|
|
81
|
+
specifier.imported.name === 'diffArrays' ||
|
|
82
|
+
specifier.imported.name === 'detailedDiff')) {
|
|
83
|
+
// Track the local name (which could be different due to renaming)
|
|
84
|
+
const localName = specifier.local.name;
|
|
85
|
+
importedFunctions.set(localName, importSource);
|
|
86
|
+
}
|
|
87
|
+
else if (importSource === 'fast-deep-equal' ||
|
|
88
|
+
importSource === 'fast-deep-equal/es6') {
|
|
89
|
+
// Handle default imports for fast-deep-equal
|
|
90
|
+
if (specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier) {
|
|
91
|
+
importedFunctions.set(specifier.local.name, importSource);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
// Report all competing diffing libraries right away
|
|
96
|
+
context.report({
|
|
97
|
+
node,
|
|
98
|
+
messageId: 'enforceMicrodiffImport',
|
|
99
|
+
data: {
|
|
100
|
+
importSource,
|
|
101
|
+
},
|
|
102
|
+
fix(fixer) {
|
|
103
|
+
// If we already have a microdiff import, just remove this import
|
|
104
|
+
if (hasMicrodiffImport) {
|
|
105
|
+
return fixer.remove(node);
|
|
106
|
+
}
|
|
107
|
+
// Otherwise, replace with microdiff import
|
|
108
|
+
return fixer.replaceText(node, `import { diff } from 'microdiff';`);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
// Check if importing a diff function or a known equality library
|
|
112
|
+
const hasDiffImport = node.specifiers.some((specifier) => {
|
|
113
|
+
if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier) {
|
|
114
|
+
return (specifier.imported.name === 'diff' ||
|
|
115
|
+
specifier.imported.name === 'diffArrays' ||
|
|
116
|
+
specifier.imported.name === 'detailedDiff');
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
});
|
|
120
|
+
if (hasDiffImport ||
|
|
121
|
+
importSource === 'fast-deep-equal' ||
|
|
122
|
+
importSource === 'fast-deep-equal/es6') {
|
|
123
|
+
importedDiffLibraries.set(importSource, node);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
// Check for usage of other diffing libraries
|
|
128
|
+
CallExpression(node) {
|
|
129
|
+
// Skip if we've already reported this node
|
|
130
|
+
if (reportedNodes.has(node)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const { callee } = node;
|
|
134
|
+
// Check for direct calls to imported diff functions
|
|
135
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
136
|
+
const name = callee.name;
|
|
137
|
+
// Check if this is a function we specifically imported from a diff library
|
|
138
|
+
if (importedFunctions.has(name)) {
|
|
139
|
+
usedImportNames.add(name);
|
|
140
|
+
// Always report it if it's from a tracked library
|
|
141
|
+
reportedNodes.add(node);
|
|
142
|
+
context.report({
|
|
143
|
+
node,
|
|
144
|
+
messageId: 'enforceMicrodiff',
|
|
145
|
+
fix(fixer) {
|
|
146
|
+
return fixer.replaceText(callee, 'diff');
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Check known diff function names
|
|
152
|
+
const isDiffFunction = [
|
|
153
|
+
'deepDiff',
|
|
154
|
+
'fastDiff',
|
|
155
|
+
'diffArrays',
|
|
156
|
+
'detailedDiff',
|
|
157
|
+
'fastDeepEqual',
|
|
158
|
+
'isEqual',
|
|
159
|
+
].includes(name);
|
|
160
|
+
if (isDiffFunction) {
|
|
161
|
+
// Track this import name as used
|
|
162
|
+
usedImportNames.add(name);
|
|
163
|
+
// Check if we have at least 2 arguments that are objects or arrays
|
|
164
|
+
if (node.arguments.length >= 2 &&
|
|
165
|
+
isObjectOrArrayType(node.arguments[0]) &&
|
|
166
|
+
isObjectOrArrayType(node.arguments[1])) {
|
|
167
|
+
reportedNodes.add(node);
|
|
168
|
+
context.report({
|
|
169
|
+
node,
|
|
170
|
+
messageId: 'enforceMicrodiff',
|
|
171
|
+
fix(fixer) {
|
|
172
|
+
// When handling fast-diff and similar libraries, need to ensure the function name is replaced
|
|
173
|
+
return fixer.replaceText(callee, 'diff');
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Check for lodash difference functions
|
|
180
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
181
|
+
callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
182
|
+
callee.object.name === '_') {
|
|
183
|
+
const property = callee.property;
|
|
184
|
+
if (property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
185
|
+
['difference', 'differenceBy', 'differenceWith'].includes(property.name)) {
|
|
186
|
+
reportedNodes.add(node);
|
|
187
|
+
context.report({
|
|
188
|
+
node,
|
|
189
|
+
messageId: 'enforceMicrodiff',
|
|
190
|
+
fix(fixer) {
|
|
191
|
+
// Replace with microdiff
|
|
192
|
+
return fixer.replaceText(node, `diff(${node.arguments
|
|
193
|
+
.map((arg) => sourceCode.getText(arg))
|
|
194
|
+
.join(', ')})`);
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
// Check for manual object comparison patterns
|
|
201
|
+
BinaryExpression(node) {
|
|
202
|
+
// Skip if we've already reported this node or its parent function
|
|
203
|
+
if (reportedNodes.has(node)) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// Find the parent function or method
|
|
207
|
+
let current = node;
|
|
208
|
+
while (current &&
|
|
209
|
+
current.type !== utils_1.AST_NODE_TYPES.FunctionDeclaration &&
|
|
210
|
+
current.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
211
|
+
current = current.parent;
|
|
212
|
+
}
|
|
213
|
+
// If we already reported the parent function, skip this node
|
|
214
|
+
if (current && reportedNodes.has(current)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// Check for JSON.stringify comparison pattern
|
|
218
|
+
if ((node.operator === '===' || node.operator === '!==') &&
|
|
219
|
+
node.left.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
220
|
+
node.right.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
221
|
+
const isJsonStringify = (expr) => {
|
|
222
|
+
return (expr.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
223
|
+
expr.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
224
|
+
expr.callee.object.name === 'JSON' &&
|
|
225
|
+
expr.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
226
|
+
expr.callee.property.name === 'stringify');
|
|
227
|
+
};
|
|
228
|
+
if (isJsonStringify(node.left) && isJsonStringify(node.right)) {
|
|
229
|
+
const leftArg = node.left.arguments[0];
|
|
230
|
+
const rightArg = node.right.arguments[0];
|
|
231
|
+
if (isObjectOrArrayType(leftArg) && isObjectOrArrayType(rightArg)) {
|
|
232
|
+
reportedNodes.add(node);
|
|
233
|
+
const isEqual = node.operator === '===';
|
|
234
|
+
context.report({
|
|
235
|
+
node,
|
|
236
|
+
messageId: 'enforceMicrodiff',
|
|
237
|
+
fix(fixer) {
|
|
238
|
+
// Find the containing function to add the import
|
|
239
|
+
let functionNode = node;
|
|
240
|
+
while (functionNode &&
|
|
241
|
+
functionNode.type !== utils_1.AST_NODE_TYPES.FunctionDeclaration &&
|
|
242
|
+
functionNode.type !==
|
|
243
|
+
utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
244
|
+
functionNode.type !== utils_1.AST_NODE_TYPES.Program) {
|
|
245
|
+
functionNode = functionNode.parent;
|
|
246
|
+
}
|
|
247
|
+
// If we found a program node and microdiff isn't imported,
|
|
248
|
+
// we'll need to add the import manually
|
|
249
|
+
if (functionNode &&
|
|
250
|
+
functionNode.type === utils_1.AST_NODE_TYPES.Program &&
|
|
251
|
+
!hasMicrodiffImport) {
|
|
252
|
+
// Need to add an import
|
|
253
|
+
const importFix = fixer.insertTextBeforeRange([0, 0], "import { diff } from 'microdiff';\n\n");
|
|
254
|
+
// Replace JSON.stringify comparison
|
|
255
|
+
const compareFix = fixer.replaceText(node, `diff(${sourceCode.getText(leftArg)}, ${sourceCode.getText(rightArg)})${isEqual ? '.length === 0' : '.length > 0'}`);
|
|
256
|
+
return [importFix, compareFix];
|
|
257
|
+
}
|
|
258
|
+
// Otherwise just replace the comparison
|
|
259
|
+
return fixer.replaceText(node, `diff(${sourceCode.getText(leftArg)}, ${sourceCode.getText(rightArg)})${isEqual ? '.length === 0' : '.length > 0'}`);
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
// Check for custom deep comparison functions
|
|
267
|
+
FunctionDeclaration(node) {
|
|
268
|
+
// Skip if we've already reported this node
|
|
269
|
+
if (reportedNodes.has(node)) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
// Look for functions that might be implementing diff logic
|
|
273
|
+
if (node.id &&
|
|
274
|
+
[
|
|
275
|
+
'detectChanges',
|
|
276
|
+
'hasConfigChanged',
|
|
277
|
+
'compareObjects',
|
|
278
|
+
'compareArrays',
|
|
279
|
+
'findChanges',
|
|
280
|
+
'detectDifferences',
|
|
281
|
+
'hasStateChanged',
|
|
282
|
+
'stateHasUpdated',
|
|
283
|
+
'arrayHasChanged',
|
|
284
|
+
'settingsChanged',
|
|
285
|
+
].includes(node.id.name)) {
|
|
286
|
+
// Check if function has two parameters that might be objects/arrays
|
|
287
|
+
if (node.params.length >= 2) {
|
|
288
|
+
const body = node.body;
|
|
289
|
+
const bodyText = sourceCode.getText(body);
|
|
290
|
+
// Check if the function body contains a JSON.stringify comparison
|
|
291
|
+
if (node.id.name === 'hasConfigChanged' &&
|
|
292
|
+
bodyText.includes('JSON.stringify') &&
|
|
293
|
+
bodyText.includes('!==')) {
|
|
294
|
+
reportedNodes.add(node);
|
|
295
|
+
const param1 = sourceCode.getText(node.params[0]);
|
|
296
|
+
const param2 = sourceCode.getText(node.params[1]);
|
|
297
|
+
context.report({
|
|
298
|
+
node,
|
|
299
|
+
messageId: 'enforceMicrodiff',
|
|
300
|
+
fix(fixer) {
|
|
301
|
+
// Create a new version of the function with microdiff
|
|
302
|
+
const newFunctionBody = `{
|
|
303
|
+
return diff(${param1}, ${param2}).length > 0;
|
|
304
|
+
}`;
|
|
305
|
+
if (!hasMicrodiffImport) {
|
|
306
|
+
// Create a new import statement
|
|
307
|
+
return fixer.replaceText(node, `import { diff } from 'microdiff';\n\nfunction ${node.id?.name}(${param1}, ${param2}) ${newFunctionBody}`);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
// Just replace the function body
|
|
311
|
+
return fixer.replaceText(body, newFunctionBody);
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
// Look for patterns that suggest object/array comparison
|
|
318
|
+
const hasComparisonLogic = bodyText.includes('JSON.stringify') ||
|
|
319
|
+
bodyText.includes('Object.keys') ||
|
|
320
|
+
bodyText.includes('for (') ||
|
|
321
|
+
bodyText.includes('.some(') ||
|
|
322
|
+
bodyText.includes('.every(');
|
|
323
|
+
if (hasComparisonLogic) {
|
|
324
|
+
reportedNodes.add(node);
|
|
325
|
+
context.report({
|
|
326
|
+
node,
|
|
327
|
+
messageId: 'enforceMicrodiff',
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
// Check for custom deep comparison in arrow functions
|
|
334
|
+
ArrowFunctionExpression(node) {
|
|
335
|
+
// Skip if we've already reported this node
|
|
336
|
+
if (reportedNodes.has(node)) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
// Only check arrow functions assigned to variables with comparison-like names
|
|
340
|
+
const parent = node.parent;
|
|
341
|
+
if (parent &&
|
|
342
|
+
parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
343
|
+
parent.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
344
|
+
[
|
|
345
|
+
'detectChanges',
|
|
346
|
+
'hasConfigChanged',
|
|
347
|
+
'compareObjects',
|
|
348
|
+
'compareArrays',
|
|
349
|
+
'findChanges',
|
|
350
|
+
'detectDifferences',
|
|
351
|
+
'hasStateChanged',
|
|
352
|
+
'stateHasUpdated',
|
|
353
|
+
'arrayHasChanged',
|
|
354
|
+
'settingsChanged',
|
|
355
|
+
].includes(parent.id.name)) {
|
|
356
|
+
// Check if function has two parameters that might be objects/arrays
|
|
357
|
+
if (node.params.length >= 2) {
|
|
358
|
+
const body = node.body;
|
|
359
|
+
// Look for patterns that suggest object/array comparison
|
|
360
|
+
const bodyText = sourceCode.getText(body);
|
|
361
|
+
const hasComparisonLogic = bodyText.includes('JSON.stringify') ||
|
|
362
|
+
bodyText.includes('Object.keys') ||
|
|
363
|
+
bodyText.includes('for (') ||
|
|
364
|
+
bodyText.includes('.some(') ||
|
|
365
|
+
bodyText.includes('.every(');
|
|
366
|
+
if (hasComparisonLogic) {
|
|
367
|
+
reportedNodes.add(node);
|
|
368
|
+
context.report({
|
|
369
|
+
node,
|
|
370
|
+
messageId: 'enforceMicrodiff',
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
//# sourceMappingURL=enforce-microdiff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const enforceMuiRoundedIcons: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"enforceRoundedVariant", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enforceMuiRoundedIcons = void 0;
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
6
|
+
exports.enforceMuiRoundedIcons = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'enforce-mui-rounded-icons',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'suggestion',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Enforce the use of -Rounded variant for MUI icons',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
enforceRoundedVariant: 'Use the -Rounded variant for MUI icons (e.g., LogoutRounded instead of Logout)',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
return {
|
|
23
|
+
ImportDeclaration(node) {
|
|
24
|
+
// Only check imports from @mui/icons-material
|
|
25
|
+
if (node.source.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
26
|
+
typeof node.source.value === 'string' &&
|
|
27
|
+
node.source.value.startsWith('@mui/icons-material/')) {
|
|
28
|
+
const iconPath = node.source.value;
|
|
29
|
+
// Skip if already using -Rounded variant
|
|
30
|
+
if (iconPath.endsWith('Rounded')) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Extract the icon name from the path
|
|
34
|
+
const iconName = iconPath.split('/').pop();
|
|
35
|
+
// Skip if the icon name is not a string (shouldn't happen)
|
|
36
|
+
if (!iconName) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Create the rounded variant name
|
|
40
|
+
const roundedVariant = `${iconName}Rounded`;
|
|
41
|
+
context.report({
|
|
42
|
+
node,
|
|
43
|
+
messageId: 'enforceRoundedVariant',
|
|
44
|
+
fix: (fixer) => {
|
|
45
|
+
// Replace the import path with the rounded variant
|
|
46
|
+
return fixer.replaceText(node.source, `'@mui/icons-material/${roundedVariant}'`);
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=enforce-mui-rounded-icons.js.map
|