@blumintinc/eslint-plugin-blumint 1.1.3 → 1.1.4
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/lib/index.js +4 -1
- package/lib/rules/avoid-utils-directory.d.ts +1 -0
- package/lib/rules/avoid-utils-directory.js +44 -0
- package/lib/rules/consistent-callback-naming.js +50 -2
- package/lib/rules/enforce-identifiable-firestore-type.js +40 -15
- package/lib/rules/extract-global-constants.js +36 -2
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -33,10 +33,11 @@ const use_custom_router_1 = require("./rules/use-custom-router");
|
|
|
33
33
|
const require_image_overlayed_1 = __importDefault(require("./rules/require-image-overlayed"));
|
|
34
34
|
const require_usememo_object_literals_1 = require("./rules/require-usememo-object-literals");
|
|
35
35
|
const enforce_safe_stringify_1 = require("./rules/enforce-safe-stringify");
|
|
36
|
+
const avoid_utils_directory_1 = require("./rules/avoid-utils-directory");
|
|
36
37
|
module.exports = {
|
|
37
38
|
meta: {
|
|
38
39
|
name: '@blumintinc/eslint-plugin-blumint',
|
|
39
|
-
version: '1.1.
|
|
40
|
+
version: '1.1.4',
|
|
40
41
|
},
|
|
41
42
|
parseOptions: {
|
|
42
43
|
ecmaVersion: 2020,
|
|
@@ -45,6 +46,7 @@ module.exports = {
|
|
|
45
46
|
recommended: {
|
|
46
47
|
plugins: ['@blumintinc/blumint'],
|
|
47
48
|
rules: {
|
|
49
|
+
'@blumintinc/blumint/avoid-utils-directory': 'error',
|
|
48
50
|
'@blumintinc/blumint/no-jsx-whitespace-literal': 'error',
|
|
49
51
|
'@blumintinc/blumint/array-methods-this-context': 'warn',
|
|
50
52
|
'@blumintinc/blumint/class-methods-read-top-to-bottom': 'warn',
|
|
@@ -109,6 +111,7 @@ module.exports = {
|
|
|
109
111
|
'require-image-overlayed': require_image_overlayed_1.default,
|
|
110
112
|
'require-usememo-object-literals': require_usememo_object_literals_1.requireUseMemoObjectLiterals,
|
|
111
113
|
'enforce-safe-stringify': enforce_safe_stringify_1.enforceStableStringify,
|
|
114
|
+
'avoid-utils-directory': avoid_utils_directory_1.avoidUtilsDirectory,
|
|
112
115
|
},
|
|
113
116
|
};
|
|
114
117
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const avoidUtilsDirectory: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"avoidUtils", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.avoidUtilsDirectory = void 0;
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
exports.avoidUtilsDirectory = (0, createRule_1.createRule)({
|
|
6
|
+
name: 'avoid-utils-directory',
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'suggestion',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'Enforce using util/ instead of utils/ directory',
|
|
11
|
+
recommended: 'error',
|
|
12
|
+
},
|
|
13
|
+
fixable: 'code',
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
avoidUtils: 'Use "util/" directory instead of "utils/"',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultOptions: [],
|
|
20
|
+
create(context) {
|
|
21
|
+
return {
|
|
22
|
+
Program(node) {
|
|
23
|
+
const filename = context.getFilename();
|
|
24
|
+
// Skip files in node_modules
|
|
25
|
+
if (filename.includes('node_modules')) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Match /utils/ directory (case insensitive) but not as part of another word
|
|
29
|
+
const utilsPattern = /(?:^|\/)utils\/(?!.*\/)/i;
|
|
30
|
+
if (utilsPattern.test(filename)) {
|
|
31
|
+
context.report({
|
|
32
|
+
node,
|
|
33
|
+
messageId: 'avoidUtils',
|
|
34
|
+
fix() {
|
|
35
|
+
// We can't provide an auto-fix since directory renaming is beyond ESLint's scope
|
|
36
|
+
return null;
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
//# sourceMappingURL=avoid-utils-directory.js.map
|
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
2
25
|
const createRule_1 = require("../utils/createRule");
|
|
26
|
+
const ts = __importStar(require("typescript"));
|
|
3
27
|
module.exports = (0, createRule_1.createRule)({
|
|
4
28
|
name: 'consistent-callback-naming',
|
|
5
29
|
meta: {
|
|
@@ -23,6 +47,29 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
23
47
|
throw new Error('You have to enable the `project` setting in parser options to use this rule');
|
|
24
48
|
}
|
|
25
49
|
const checker = parserServices.program.getTypeChecker();
|
|
50
|
+
function isReactComponentType(node) {
|
|
51
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
52
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
53
|
+
const symbol = type.getSymbol();
|
|
54
|
+
if (!symbol)
|
|
55
|
+
return false;
|
|
56
|
+
// Check if type is a React component type
|
|
57
|
+
const isComponent = symbol.declarations?.some((decl) => {
|
|
58
|
+
const declaration = decl;
|
|
59
|
+
if (ts.isClassDeclaration(declaration) ||
|
|
60
|
+
ts.isInterfaceDeclaration(declaration)) {
|
|
61
|
+
const name = declaration.name?.text ?? '';
|
|
62
|
+
return (
|
|
63
|
+
// Check for common React component patterns
|
|
64
|
+
name.includes('Component') ||
|
|
65
|
+
name.includes('Element') ||
|
|
66
|
+
name.includes('FC') ||
|
|
67
|
+
name.includes('FunctionComponent'));
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
});
|
|
71
|
+
return isComponent || false;
|
|
72
|
+
}
|
|
26
73
|
function isFunctionType(node) {
|
|
27
74
|
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
28
75
|
const type = checker.getTypeAtLocation(tsNode);
|
|
@@ -38,10 +85,11 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
38
85
|
if (propName?.match(/^on[A-Z]/)) {
|
|
39
86
|
return;
|
|
40
87
|
}
|
|
41
|
-
// Check if the value is a function type
|
|
88
|
+
// Check if the value is a function type and not a React component
|
|
42
89
|
if (isFunctionType(node.value.expression) &&
|
|
43
90
|
propName &&
|
|
44
|
-
!propName.startsWith('on')
|
|
91
|
+
!propName.startsWith('on') &&
|
|
92
|
+
!isReactComponentType(node.value.expression)) {
|
|
45
93
|
context.report({
|
|
46
94
|
node,
|
|
47
95
|
messageId: 'callbackPropPrefix',
|
|
@@ -63,25 +63,50 @@ exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
|
|
|
63
63
|
TSTypeAliasDeclaration(node) {
|
|
64
64
|
if (node.id.name === folderName) {
|
|
65
65
|
hasExpectedType = true;
|
|
66
|
-
//
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
type.typeName.name
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
// Find Identifiable in type's dependencies
|
|
67
|
+
const findIdentifiable = (type, checkedTypes = new Set()) => {
|
|
68
|
+
if (!type)
|
|
69
|
+
return false;
|
|
70
|
+
if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference && type.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
71
|
+
const typeName = type.typeName.name;
|
|
72
|
+
if (typeName === 'Identifiable') {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (!checkedTypes.has(typeName)) {
|
|
76
|
+
checkedTypes.add(typeName);
|
|
77
|
+
// Look for the type in all scopes
|
|
78
|
+
const scope = context.getScope();
|
|
79
|
+
const variable = scope.variables.find(v => v.name === typeName);
|
|
80
|
+
if (variable) {
|
|
81
|
+
const def = variable.defs.find(d => d.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
|
|
82
|
+
if (def && 'typeAnnotation' in def.node && def.node.typeAnnotation) {
|
|
83
|
+
return findIdentifiable(def.node.typeAnnotation, checkedTypes);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Try looking in the parent scope
|
|
87
|
+
if (scope.upper) {
|
|
88
|
+
const parentVariable = scope.upper.variables.find(v => v.name === typeName);
|
|
89
|
+
if (parentVariable) {
|
|
90
|
+
const def = parentVariable.defs.find(d => d.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
|
|
91
|
+
if (def && 'typeAnnotation' in def.node && def.node.typeAnnotation) {
|
|
92
|
+
return findIdentifiable(def.node.typeAnnotation, checkedTypes);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
77
97
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
type.
|
|
81
|
-
return type.typeParameters.params.some(checkIdentifiableExtension);
|
|
98
|
+
else if (type.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
|
|
99
|
+
// For intersection types, check each part
|
|
100
|
+
return type.types.some(part => findIdentifiable(part, checkedTypes));
|
|
82
101
|
}
|
|
83
102
|
return false;
|
|
84
103
|
};
|
|
104
|
+
// Check if type extends Identifiable
|
|
105
|
+
const checkIdentifiableExtension = (type) => {
|
|
106
|
+
if (!type)
|
|
107
|
+
return false;
|
|
108
|
+
return findIdentifiable(type);
|
|
109
|
+
};
|
|
85
110
|
// Check if type has id: string field
|
|
86
111
|
const checkIdField = (type) => {
|
|
87
112
|
// Check for id: string field in type literal
|
|
@@ -19,6 +19,40 @@ function isFunctionDefinition(node) {
|
|
|
19
19
|
return (node?.type === 'FunctionExpression' ||
|
|
20
20
|
node?.type === 'ArrowFunctionExpression');
|
|
21
21
|
}
|
|
22
|
+
function isMutableValue(node) {
|
|
23
|
+
if (!node)
|
|
24
|
+
return false;
|
|
25
|
+
// Check for array literals and object expressions
|
|
26
|
+
if (node.type === 'ArrayExpression' || node.type === 'ObjectExpression') {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
// Check for new expressions (e.g., new Map(), new Set())
|
|
30
|
+
if (node.type === 'NewExpression') {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
// Check for array/object methods that return mutable values
|
|
34
|
+
if (node.type === 'CallExpression') {
|
|
35
|
+
const callee = node.callee;
|
|
36
|
+
if (callee.type === 'MemberExpression') {
|
|
37
|
+
const methodName = callee.property.name;
|
|
38
|
+
const mutatingMethods = [
|
|
39
|
+
'slice',
|
|
40
|
+
'map',
|
|
41
|
+
'filter',
|
|
42
|
+
'concat',
|
|
43
|
+
'from',
|
|
44
|
+
'reduce',
|
|
45
|
+
'flatMap',
|
|
46
|
+
'splice',
|
|
47
|
+
'reverse',
|
|
48
|
+
'sort',
|
|
49
|
+
'fill'
|
|
50
|
+
];
|
|
51
|
+
return mutatingMethods.includes(methodName);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
22
56
|
exports.extractGlobalConstants = (0, createRule_1.createRule)({
|
|
23
57
|
create(context) {
|
|
24
58
|
return {
|
|
@@ -26,8 +60,8 @@ exports.extractGlobalConstants = (0, createRule_1.createRule)({
|
|
|
26
60
|
if (node.kind !== 'const') {
|
|
27
61
|
return;
|
|
28
62
|
}
|
|
29
|
-
// Skip if any of the declarations are function definitions
|
|
30
|
-
if (node.declarations.some((d) => isFunctionDefinition(d.init))) {
|
|
63
|
+
// Skip if any of the declarations are function definitions or mutable values
|
|
64
|
+
if (node.declarations.some((d) => isFunctionDefinition(d.init) || isMutableValue(d.init))) {
|
|
31
65
|
return;
|
|
32
66
|
}
|
|
33
67
|
const scope = context.getScope();
|