@blumintinc/eslint-plugin-blumint 1.1.2 → 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/README.md CHANGED
@@ -56,24 +56,37 @@ Or use the recommended config:
56
56
  ✅ Set in the `recommended` configuration.\
57
57
  🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).
58
58
 
59
- | Name                             | Description | 💼 | ⚠️ | 🔧 |
60
- | :--------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- |
61
- | [array-methods-this-context](docs/rules/array-methods-this-context.md) | Prevent misuse of Array methods in OOP | | ✅ | |
62
- | [class-methods-read-top-to-bottom](docs/rules/class-methods-read-top-to-bottom.md) | Ensures classes read linearly from top to bottom. | | ✅ | 🔧 |
63
- | [dynamic-https-errors](docs/rules/dynamic-https-errors.md) | Dynamic error details should only be in the third argument of the HttpsError constructor. The second argument is hashed to produce a unique id. | | ✅ | |
64
- | [export-if-in-doubt](docs/rules/export-if-in-doubt.md) | All top-level const definitions, type definitions, and functions should be exported | | | |
65
- | [extract-global-constants](docs/rules/extract-global-constants.md) | Extract constants/functions to the global scope when possible | | | |
66
- | [generic-starts-with-t](docs/rules/generic-starts-with-t.md) | Enforce TypeScript generic types to start with T | | ✅ | |
67
- | [global-const-style](docs/rules/global-const-style.md) | Enforce UPPER_SNAKE_CASE and as const for global static constants | ✅ | | 🔧 |
68
- | [no-async-array-filter](docs/rules/no-async-array-filter.md) | Disallow async callbacks for Array.filter | ✅ | | |
69
- | [no-async-foreach](docs/rules/no-async-foreach.md) | Disallow Array.forEach with an async callback function | ✅ | | |
70
- | [no-conditional-literals-in-jsx](docs/rules/no-conditional-literals-in-jsx.md) | Disallow use of conditional literals in JSX code | | | |
71
- | [no-filter-without-return](docs/rules/no-filter-without-return.md) | Disallow Array.filter callbacks without an explicit return (if part of a block statement) | ✅ | | |
72
- | [no-misused-switch-case](docs/rules/no-misused-switch-case.md) | Prevent misuse of logical OR in switch case statements | ✅ | | |
73
- | [no-unpinned-dependencies](docs/rules/no-unpinned-dependencies.md) | Enforces pinned dependencies | ✅ | | 🔧 |
74
- | [no-useless-fragment](docs/rules/no-useless-fragment.md) | Prevent unnecessary use of React fragments | | ✅ | 🔧 |
75
- | [prefer-fragment-shorthand](docs/rules/prefer-fragment-shorthand.md) | Prefer <> shorthand for <React.Fragment> | | ✅ | 🔧 |
76
- | [prefer-type-over-interface](docs/rules/prefer-type-over-interface.md) | Prefer using type alias over interface | | ✅ | 🔧 |
77
- | [require-memo](docs/rules/require-memo.md) | React components must be memoized | ✅ | | 🔧 |
59
+ | Name                                | Description | 💼 | ⚠️ | 🔧 |
60
+ | :--------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- |
61
+ | [array-methods-this-context](docs/rules/array-methods-this-context.md) | Prevent misuse of Array methods in OOP | | ✅ | |
62
+ | [class-methods-read-top-to-bottom](docs/rules/class-methods-read-top-to-bottom.md) | Ensures classes read linearly from top to bottom. | | ✅ | 🔧 |
63
+ | [consistent-callback-naming](docs/rules/consistent-callback-naming.md) | Enforce consistent naming conventions for callback props and functions | ✅ | | 🔧 |
64
+ | [dynamic-https-errors](docs/rules/dynamic-https-errors.md) | Dynamic error details should only be in the third argument of the HttpsError constructor. The second argument is hashed to produce a unique id. | || |
65
+ | [enforce-callable-types](docs/rules/enforce-callable-types.md) | Enforce Params and Response type exports in callable functions || | |
66
+ | [enforce-callback-memo](docs/rules/enforce-callback-memo.md) | Enforce useCallback or useMemo for inline functions in JSX props | ✅ | | |
67
+ | [enforce-dynamic-firebase-imports](docs/rules/enforce-dynamic-firebase-imports.md) | Enforce dynamic importing for modules within the firebaseCloud directory | ✅ | | |
68
+ | [enforce-identifiable-firestore-type](docs/rules/enforce-identifiable-firestore-type.md) | Enforce that Firestore type definitions extend Identifiable and match their folder name | ✅ | | |
69
+ | [enforce-safe-stringify](docs/rules/enforce-safe-stringify.md) | Enforce using safe-stable-stringify instead of JSON.stringify | ✅ | | 🔧 |
70
+ | [export-if-in-doubt](docs/rules/export-if-in-doubt.md) | All top-level const definitions, type definitions, and functions should be exported | | | |
71
+ | [extract-global-constants](docs/rules/extract-global-constants.md) | Extract static constants and functions to the global scope when possible | | ✅ | |
72
+ | [generic-starts-with-t](docs/rules/generic-starts-with-t.md) | Enforce TypeScript generic types to start with T | | ✅ | |
73
+ | [global-const-style](docs/rules/global-const-style.md) | Enforce UPPER_SNAKE_CASE and as const for global static constants | ✅ | | 🔧 |
74
+ | [no-async-array-filter](docs/rules/no-async-array-filter.md) | Disallow async callbacks for Array.filter | ✅ | | |
75
+ | [no-async-foreach](docs/rules/no-async-foreach.md) | Disallow Array.forEach with an async callback function | ✅ | | |
76
+ | [no-conditional-literals-in-jsx](docs/rules/no-conditional-literals-in-jsx.md) | Disallow use of conditional literals in JSX code | ✅ | | |
77
+ | [no-filter-without-return](docs/rules/no-filter-without-return.md) | Disallow Array.filter callbacks without an explicit return (if part of a block statement) | ✅ | | |
78
+ | [no-jsx-whitespace-literal](docs/rules/no-jsx-whitespace-literal.md) | Disallow the use of {" "} elements in JSX code | ✅ | | |
79
+ | [no-misused-switch-case](docs/rules/no-misused-switch-case.md) | Prevent misuse of logical OR in switch case statements | ✅ | | |
80
+ | [no-unpinned-dependencies](docs/rules/no-unpinned-dependencies.md) | Enforces pinned dependencies | ✅ | | 🔧 |
81
+ | [no-unused-props](docs/rules/no-unused-props.md) | Detect unused props in React component type definitions | ✅ | | 🔧 |
82
+ | [no-useless-fragment](docs/rules/no-useless-fragment.md) | Prevent unnecessary use of React fragments | | ✅ | 🔧 |
83
+ | [prefer-fragment-shorthand](docs/rules/prefer-fragment-shorthand.md) | Prefer <> shorthand for <React.Fragment> | | ✅ | 🔧 |
84
+ | [prefer-type-over-interface](docs/rules/prefer-type-over-interface.md) | Prefer using type alias over interface | | ✅ | 🔧 |
85
+ | [require-dynamic-firebase-imports](docs/rules/require-dynamic-firebase-imports.md) | Enforce dynamic imports for Firebase dependencies | ✅ | | 🔧 |
86
+ | [require-https-error](docs/rules/require-https-error.md) | Enforce using proprietary HttpsError instead of throw new Error or firebase-admin HttpsError in functions/src | ✅ | | |
87
+ | [require-image-overlayed](docs/rules/require-image-overlayed.md) | Enforce using ImageOverlayed component instead of next/image or img tags | ✅ | | 🔧 |
88
+ | [require-memo](docs/rules/require-memo.md) | React components must be memoized | ✅ | | 🔧 |
89
+ | [require-usememo-object-literals](docs/rules/require-usememo-object-literals.md) | Enforce using useMemo for inline object literals passed as props to JSX components | ✅ | | |
90
+ | [use-custom-router](docs/rules/use-custom-router.md) | Enforce using src/hooks/routing/useRouter instead of next/router | ✅ | | 🔧 |
78
91
 
79
92
  <!-- end auto-generated rules list -->
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.2',
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.2",
3
+ "version": "1.1.4",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",