@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 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.3',
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
- // Check if type extends Identifiable
67
- const checkIdentifiableExtension = (type) => {
68
- // Check for direct Identifiable extension
69
- if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
70
- type.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
71
- type.typeName.name === 'Identifiable') {
72
- return true;
73
- }
74
- // Check intersection types
75
- if (type.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
76
- return type.types.some(checkIdentifiableExtension);
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
- // Check generic type parameters
79
- if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
80
- type.typeParameters?.params) {
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",