mirror of
https://github.com/luxexchange/eslint-config.git
synced 2026-10-30 18:13:47 +00:00
166 lines
4.8 KiB
JavaScript
166 lines
4.8 KiB
JavaScript
'use strict'
|
|
|
|
const { isHook } = require('../utils/isHook')
|
|
|
|
module.exports = {
|
|
meta: {
|
|
type: 'problem',
|
|
docs: {
|
|
description: 'Enforce using QueryOptionsResult type for functions returning query options',
|
|
category: 'Best Practices',
|
|
recommended: true,
|
|
},
|
|
fixable: 'code',
|
|
schema: [
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
importPath: {
|
|
type: 'string',
|
|
default: '@l.x/utils/src/reactQuery/queryOptions',
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
],
|
|
messages: {
|
|
useQueryOptionsResult: 'Use QueryOptionsResult type instead of {{actualType}} for query option return types',
|
|
},
|
|
},
|
|
|
|
create(context) {
|
|
const options = context.options[0] || {}
|
|
const importPath = options.importPath || '@l.x/utils/src/reactQuery/queryOptions'
|
|
const sourceCode = context.getSourceCode()
|
|
|
|
// Track imports state
|
|
let hasQueryOptionsResultImport = false
|
|
let existingImportNode = null
|
|
|
|
// Track processed functions to avoid duplicate reports
|
|
const processedFunctions = new WeakSet()
|
|
|
|
// Use Set for O(1) lookup performance
|
|
const prohibitedTypes = new Set(['UseQueryOptions', 'UseQueryResult', 'QueryOptions'])
|
|
|
|
function checkFunctionReturnType(node) {
|
|
// Skip if already processed
|
|
if (processedFunctions.has(node)) {
|
|
return
|
|
}
|
|
processedFunctions.add(node)
|
|
|
|
// Early return if no return type annotation
|
|
if (!node.returnType?.typeAnnotation) {
|
|
return
|
|
}
|
|
|
|
// Skip hooks (functions whose names start with "use")
|
|
if (isHook(node)) {
|
|
return
|
|
}
|
|
|
|
const typeAnnotation = node.returnType.typeAnnotation
|
|
|
|
// Only check TSTypeReference nodes
|
|
if (typeAnnotation.type !== 'TSTypeReference' || !typeAnnotation.typeName) {
|
|
return
|
|
}
|
|
|
|
const typeName =
|
|
typeAnnotation.typeName.type === 'Identifier'
|
|
? typeAnnotation.typeName.name
|
|
: sourceCode.getText(typeAnnotation.typeName)
|
|
|
|
if (prohibitedTypes.has(typeName)) {
|
|
context.report({
|
|
node: node.returnType,
|
|
messageId: 'useQueryOptionsResult',
|
|
data: { actualType: typeName },
|
|
fix(fixer) {
|
|
const fixes = [fixer.replaceText(typeAnnotation.typeName, 'QueryOptionsResult')]
|
|
|
|
// Add import if needed
|
|
if (!hasQueryOptionsResultImport) {
|
|
fixes.push(...getImportFix(fixer))
|
|
}
|
|
|
|
return fixes
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
function getImportFix(fixer) {
|
|
if (existingImportNode) {
|
|
// Add to existing import from correct path
|
|
const lastSpecifier = existingImportNode.specifiers[existingImportNode.specifiers.length - 1]
|
|
return [fixer.insertTextAfter(lastSpecifier, ', QueryOptionsResult')]
|
|
}
|
|
|
|
// Add new import at the top
|
|
const insertText = `import { QueryOptionsResult } from '${importPath}'\n`
|
|
const firstImport = sourceCode.ast.body.find((node) => node.type === 'ImportDeclaration')
|
|
|
|
return [
|
|
firstImport ? fixer.insertTextBefore(firstImport, insertText) : fixer.insertTextBeforeRange([0, 0], insertText),
|
|
]
|
|
}
|
|
|
|
return {
|
|
ImportDeclaration(node) {
|
|
const importValue = node.source?.value
|
|
if (!importValue) {
|
|
return
|
|
}
|
|
|
|
// Check if this import is from our utilities path
|
|
if (
|
|
importValue.includes('reactQuery/queryOptions') ||
|
|
importValue.includes('@l.x/utils/src/reactQuery') ||
|
|
importValue.includes('utilities/reactQuery')
|
|
) {
|
|
existingImportNode = node
|
|
|
|
// Check if QueryOptionsResult is already imported
|
|
const hasQueryOptionsResult = node.specifiers.some(
|
|
(spec) => spec.type === 'ImportSpecifier' && spec.imported.name === 'QueryOptionsResult',
|
|
)
|
|
if (hasQueryOptionsResult) {
|
|
hasQueryOptionsResultImport = true
|
|
}
|
|
}
|
|
},
|
|
|
|
FunctionDeclaration(node) {
|
|
checkFunctionReturnType(node)
|
|
},
|
|
|
|
FunctionExpression(node) {
|
|
checkFunctionReturnType(node)
|
|
},
|
|
|
|
ArrowFunctionExpression(node) {
|
|
checkFunctionReturnType(node)
|
|
},
|
|
|
|
// Check exported functions
|
|
ExportNamedDeclaration(node) {
|
|
if (!node.declaration) {
|
|
return
|
|
}
|
|
|
|
if (node.declaration.type === 'FunctionDeclaration') {
|
|
checkFunctionReturnType(node.declaration)
|
|
} else if (node.declaration.type === 'VariableDeclaration') {
|
|
node.declaration.declarations.forEach((decl) => {
|
|
if (decl.init?.type === 'FunctionExpression' || decl.init?.type === 'ArrowFunctionExpression') {
|
|
checkFunctionReturnType(decl.init)
|
|
}
|
|
})
|
|
}
|
|
},
|
|
}
|
|
},
|
|
}
|