@blumintinc/eslint-plugin-blumint 1.5.3 → 1.5.5

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
@@ -59,7 +59,7 @@ const no_jsx_in_hooks_1 = require("./rules/no-jsx-in-hooks");
59
59
  module.exports = {
60
60
  meta: {
61
61
  name: '@blumintinc/eslint-plugin-blumint',
62
- version: '1.5.3',
62
+ version: '1.5.5',
63
63
  },
64
64
  parseOptions: {
65
65
  ecmaVersion: 2020,
@@ -4581,29 +4581,77 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
4581
4581
  }
4582
4582
  return isVerb;
4583
4583
  }
4584
+ function isPascalCase(name) {
4585
+ return /^[A-Z][a-zA-Z0-9]*$/.test(name);
4586
+ }
4587
+ function hasPropsParameter(node) {
4588
+ if (!node.params.length)
4589
+ return false;
4590
+ const firstParam = node.params[0];
4591
+ if (firstParam.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
4592
+ return true; // Destructured props
4593
+ }
4594
+ if (firstParam.type === utils_1.AST_NODE_TYPES.Identifier) {
4595
+ const paramName = firstParam.name.toLowerCase();
4596
+ return paramName === 'props';
4597
+ }
4598
+ return false;
4599
+ }
4600
+ function hasJsxReturn(node) {
4601
+ const sourceCode = context.getSourceCode();
4602
+ const text = sourceCode.getText(node);
4603
+ // Check for direct JSX returns
4604
+ if (text.includes('return <') || text.includes('=> <')) {
4605
+ return true;
4606
+ }
4607
+ // Check for JSX assigned to variables then returned
4608
+ if (text.includes('const ') && text.includes('<') && text.includes('return')) {
4609
+ return true;
4610
+ }
4611
+ return false;
4612
+ }
4584
4613
  function isReactComponent(node) {
4585
4614
  if (node.type !== utils_1.AST_NODE_TYPES.FunctionDeclaration &&
4586
4615
  node.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
4587
4616
  node.type !== utils_1.AST_NODE_TYPES.FunctionExpression) {
4588
4617
  return false;
4589
4618
  }
4590
- // Check if function has React component type annotation
4591
- if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
4619
+ // Get the function name
4620
+ let functionName = '';
4621
+ if (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration && node.id) {
4622
+ functionName = node.id.name;
4623
+ }
4624
+ else if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression || node.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
4592
4625
  const parent = node.parent;
4593
- if (parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
4594
- const id = parent.id;
4595
- if (id.type === utils_1.AST_NODE_TYPES.Identifier && id.typeAnnotation?.type === utils_1.AST_NODE_TYPES.TSTypeAnnotation) {
4596
- const typeText = context.getSourceCode().getText(id.typeAnnotation.typeAnnotation);
4597
- if (typeText.includes('React.') || typeText.includes('FC') || typeText.includes('FunctionComponent')) {
4598
- return true;
4599
- }
4600
- }
4626
+ if (parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator && parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
4627
+ functionName = parent.id.name;
4601
4628
  }
4602
4629
  }
4603
- // Check if function returns JSX
4604
- const sourceCode = context.getSourceCode();
4605
- const text = sourceCode.getText(node);
4606
- return text.includes('return <') || text.includes('=> <');
4630
+ // Check for React component indicators
4631
+ const indicators = [
4632
+ // 1. PascalCase naming
4633
+ functionName && isPascalCase(functionName),
4634
+ // 2. Props parameter
4635
+ hasPropsParameter(node),
4636
+ // 3. JSX return
4637
+ hasJsxReturn(node),
4638
+ // 4. React type annotations
4639
+ (() => {
4640
+ if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
4641
+ const parent = node.parent;
4642
+ if (parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
4643
+ const id = parent.id;
4644
+ if (id.type === utils_1.AST_NODE_TYPES.Identifier && id.typeAnnotation?.type === utils_1.AST_NODE_TYPES.TSTypeAnnotation) {
4645
+ const typeText = context.getSourceCode().getText(id.typeAnnotation.typeAnnotation);
4646
+ return typeText.includes('React.') || typeText.includes('FC') || typeText.includes('FunctionComponent');
4647
+ }
4648
+ }
4649
+ }
4650
+ return false;
4651
+ })()
4652
+ ];
4653
+ // Consider it a React component if it matches at least 2 indicators
4654
+ return indicators.filter(Boolean).length >= 2;
4607
4655
  }
4608
4656
  return {
4609
4657
  FunctionDeclaration(node) {
@@ -87,12 +87,23 @@ function isInterfaceOrAbstractMethodSignature(node) {
87
87
  }
88
88
  return false;
89
89
  }
90
+ function isTypeGuardFunction(node) {
91
+ if (!('returnType' in node) || !node.returnType)
92
+ return false;
93
+ const returnType = node.returnType;
94
+ if (returnType.type !== utils_1.AST_NODE_TYPES.TSTypeAnnotation)
95
+ return false;
96
+ const typeAnnotation = returnType.typeAnnotation;
97
+ if (typeAnnotation.type !== utils_1.AST_NODE_TYPES.TSTypePredicate)
98
+ return false;
99
+ return true;
100
+ }
90
101
  exports.noExplicitReturnType = (0, createRule_1.createRule)({
91
102
  name: 'no-explicit-return-type',
92
103
  meta: {
93
104
  type: 'suggestion',
94
105
  docs: {
95
- description: 'Disallow explicit return type annotations on functions when TypeScript can infer them. This reduces code verbosity and maintenance burden while leveraging TypeScript\'s powerful type inference. Exceptions are made for recursive functions, overloaded functions, interface methods, and abstract methods where explicit types improve clarity.',
106
+ description: 'Disallow explicit return type annotations on functions when TypeScript can infer them. This reduces code verbosity and maintenance burden while leveraging TypeScript\'s powerful type inference. Exceptions are made for type guard functions (using the `is` keyword), recursive functions, overloaded functions, interface methods, and abstract methods where explicit types improve clarity.',
96
107
  recommended: 'error',
97
108
  requiresTypeChecking: false,
98
109
  extendsBaseRule: false,
@@ -136,8 +147,8 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
136
147
  FunctionDeclaration(node) {
137
148
  if (!node.returnType)
138
149
  return;
139
- if (mergedOptions.allowRecursiveFunctions &&
140
- isRecursiveFunction(node)) {
150
+ if (isTypeGuardFunction(node) ||
151
+ (mergedOptions.allowRecursiveFunctions && isRecursiveFunction(node))) {
141
152
  return;
142
153
  }
143
154
  context.report({
@@ -149,8 +160,8 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
149
160
  FunctionExpression(node) {
150
161
  if (!node.returnType)
151
162
  return;
152
- if (mergedOptions.allowRecursiveFunctions &&
153
- isRecursiveFunction(node)) {
163
+ if (isTypeGuardFunction(node) ||
164
+ (mergedOptions.allowRecursiveFunctions && isRecursiveFunction(node))) {
154
165
  return;
155
166
  }
156
167
  context.report({
@@ -162,6 +173,9 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
162
173
  ArrowFunctionExpression(node) {
163
174
  if (!node.returnType)
164
175
  return;
176
+ if (isTypeGuardFunction(node)) {
177
+ return;
178
+ }
165
179
  context.report({
166
180
  node: node.returnType,
167
181
  messageId: 'noExplicitReturnType',
@@ -39,9 +39,35 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
39
39
  typeNode.types.forEach(extractProps);
40
40
  }
41
41
  else if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
42
- // For referenced types like FormControlLabelProps, we need to track that these props should be forwarded
43
42
  if (typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
44
- props[`...${typeNode.typeName.name}`] = typeNode.typeName;
43
+ if (typeNode.typeName.name === 'Pick' && typeNode.typeParameters) {
44
+ // Handle Pick utility type
45
+ const [baseType, pickedProps] = typeNode.typeParameters.params;
46
+ if (baseType.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
47
+ baseType.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
48
+ // Extract the picked properties from the union type
49
+ if (pickedProps.type === utils_1.AST_NODE_TYPES.TSUnionType) {
50
+ pickedProps.types.forEach((type) => {
51
+ if (type.type === utils_1.AST_NODE_TYPES.TSLiteralType &&
52
+ type.literal.type === utils_1.AST_NODE_TYPES.Literal &&
53
+ typeof type.literal.value === 'string') {
54
+ // Add each picked property as a regular prop
55
+ props[type.literal.value] = type.literal;
56
+ }
57
+ });
58
+ }
59
+ else if (pickedProps.type === utils_1.AST_NODE_TYPES.TSLiteralType &&
60
+ pickedProps.literal.type === utils_1.AST_NODE_TYPES.Literal &&
61
+ typeof pickedProps.literal.value === 'string') {
62
+ // Single property pick
63
+ props[pickedProps.literal.value] = pickedProps.literal;
64
+ }
65
+ }
66
+ }
67
+ else {
68
+ // For referenced types like FormControlLabelProps, we need to track that these props should be forwarded
69
+ props[`...${typeNode.typeName.name}`] = typeNode.typeName;
70
+ }
45
71
  }
46
72
  }
47
73
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.5.3",
3
+ "version": "1.5.5",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",