UNPKG

eslint-plugin-fsecond

Version:
665 lines (657 loc) 27.8 kB
import { ASTUtils, AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils"; import * as ts from "typescript"; //#region package.json var name = "eslint-plugin-fsecond"; var version = "0.0.0-development"; //#endregion //#region src/utils.ts const createEslintRule = ESLintUtils.RuleCreator((ruleName) => ruleName); //#endregion //#region src/rules/no-inline-interfaces.ts const RULE_NAME$3 = "no-inline-interfaces"; const isStatementParent = ASTUtils.isNodeOfTypes([ AST_NODE_TYPES.Program, AST_NODE_TYPES.BlockStatement, AST_NODE_TYPES.TSModuleBlock ]); const findStatementAncestor = (node) => { if (!node.parent) return null; if (isStatementParent(node.parent)) return node; return findStatementAncestor(node.parent); }; /** * Recursively find all TSTypeLiteral nodes in a type annotation. * Returns all inline object type literals that should be reported. */ const findTypeLiterals = (type, results = [], checkGenericTypes = false) => { if (!type) return results; switch (type.type) { case AST_NODE_TYPES.TSTypeLiteral: results.push(type); type.members.forEach((member) => { if (member.type === AST_NODE_TYPES.TSPropertySignature && member.typeAnnotation) findTypeLiterals(member.typeAnnotation.typeAnnotation, results, checkGenericTypes); }); return results; case AST_NODE_TYPES.TSUnionType: case AST_NODE_TYPES.TSIntersectionType: for (const typeNode of type.types) findTypeLiterals(typeNode, results, checkGenericTypes); break; case AST_NODE_TYPES.TSTypeReference: if (checkGenericTypes && type.typeArguments) type.typeArguments.params.forEach((param) => { findTypeLiterals(param, results, checkGenericTypes); }); break; case AST_NODE_TYPES.TSArrayType: findTypeLiterals(type.elementType, results, checkGenericTypes); break; case AST_NODE_TYPES.TSTupleType: type.elementTypes.forEach((elementType) => { findTypeLiterals(elementType, results, checkGenericTypes); }); break; case AST_NODE_TYPES.TSOptionalType: findTypeLiterals(type.typeAnnotation, results, checkGenericTypes); break; case AST_NODE_TYPES.TSRestType: findTypeLiterals(type.typeAnnotation, results, checkGenericTypes); break; case AST_NODE_TYPES.TSTypeOperator: findTypeLiterals(type.typeAnnotation, results, checkGenericTypes); break; case AST_NODE_TYPES.TSIndexedAccessType: findTypeLiterals(type.objectType, results, checkGenericTypes); findTypeLiterals(type.indexType, results, checkGenericTypes); break; case AST_NODE_TYPES.TSConditionalType: findTypeLiterals(type.trueType, results, checkGenericTypes); findTypeLiterals(type.falseType, results, checkGenericTypes); findTypeLiterals(type.checkType, results, checkGenericTypes); findTypeLiterals(type.extendsType, results, checkGenericTypes); break; case AST_NODE_TYPES.TSConstructorType: case AST_NODE_TYPES.TSFunctionType: type.params.forEach((param) => { if (param.type === AST_NODE_TYPES.TSParameterProperty) findTypeLiterals(param.parameter.typeAnnotation?.typeAnnotation, results, checkGenericTypes); else if (param.typeAnnotation) findTypeLiterals(param.typeAnnotation.typeAnnotation, results, checkGenericTypes); }); findTypeLiterals(type.returnType?.typeAnnotation, results, checkGenericTypes); break; case AST_NODE_TYPES.TSImportType: if (type.typeArguments) type.typeArguments.params.forEach((param) => { findTypeLiterals(param, results, checkGenericTypes); }); break; case AST_NODE_TYPES.TSMappedType: findTypeLiterals(type.typeAnnotation, results, checkGenericTypes); if (type.nameType) findTypeLiterals(type.nameType, results, checkGenericTypes); break; case AST_NODE_TYPES.TSNamedTupleMember: findTypeLiterals(type.elementType, results, checkGenericTypes); break; case AST_NODE_TYPES.TSTypePredicate: if (type.typeAnnotation) findTypeLiterals(type.typeAnnotation.typeAnnotation, results, checkGenericTypes); break; default: break; } return results; }; const isClassNode = ASTUtils.isNodeOfTypes([AST_NODE_TYPES.ClassDeclaration, AST_NODE_TYPES.ClassExpression]); /** * Check if a node is inside a class. */ const isInsideClass = (node) => { if (!node.parent) return false; if (isClassNode(node.parent)) return true; return isInsideClass(node.parent); }; var no_inline_interfaces_default = createEslintRule({ name: RULE_NAME$3, meta: { type: "suggestion", docs: { description: "disallow inline object type literals in variable and function annotations; extract to a named interface or type alias.", url: "https://github.com/AndreaPontrandolfo/eslint-plugin-fsecond/blob/master/docs/rules/no-inline-interfaces.md", recommended: true }, fixable: "code", schema: [{ type: "object", properties: { checkGenericTypes: { type: "boolean", description: "Check inline object types within generic type arguments (e.g., Array<{ a: string }>)" }, checkReturnTypes: { type: "boolean", description: "Check inline object types in function return type annotations" } }, additionalProperties: false }], defaultOptions: [{ checkGenericTypes: false, checkReturnTypes: false }], messages: { noInlineInterfaces: "Extract this inline object type into a named interface or type alias and reference it here." } }, defaultOptions: [{ checkGenericTypes: false, checkReturnTypes: false }], create(context) { const options = context.options[0] ?? {}; const checkGenericTypes = options.checkGenericTypes ?? false; const checkReturnTypes = options.checkReturnTypes ?? false; let interfaceCounter = 0; /** * Report all inline object type literals found in a type annotation. */ const reportTypeAnnotation = (typeAnnotation) => { const literals = findTypeLiterals(typeAnnotation, [], checkGenericTypes); for (const literal of literals) { interfaceCounter = interfaceCounter + 1; const interfaceName = interfaceCounter === 1 ? "InlineInterface" : `InlineInterface${String(interfaceCounter)}`; context.report({ node: literal, messageId: "noInlineInterfaces", fix(fixer) { const statementNode = findStatementAncestor(literal); if (!statementNode) return null; const { sourceCode } = context; const openBrace = sourceCode.getFirstToken(literal); const closeBrace = sourceCode.getLastToken(literal); if (!openBrace || !closeBrace) return null; const bodyText = sourceCode.getText().slice(openBrace.range[1], closeBrace.range[0]); return [fixer.insertTextBefore(statementNode, `interface ${interfaceName} {${bodyText}}\n`), fixer.replaceText(literal, interfaceName)]; } }); } }; /** * Check a parameter node for type annotations * Handles both direct parameters and parameters with default values (AssignmentPattern). */ const checkParameter = (param) => { let nodeToCheck = param; if (param.type === AST_NODE_TYPES.AssignmentPattern) nodeToCheck = param.left; if ((nodeToCheck.type === AST_NODE_TYPES.Identifier || nodeToCheck.type === AST_NODE_TYPES.ArrayPattern || nodeToCheck.type === AST_NODE_TYPES.ObjectPattern || nodeToCheck.type === AST_NODE_TYPES.RestElement) && nodeToCheck.typeAnnotation) reportTypeAnnotation(nodeToCheck.typeAnnotation.typeAnnotation); }; return { VariableDeclarator(node) { if (isInsideClass(node)) return; if (node.id.typeAnnotation) reportTypeAnnotation(node.id.typeAnnotation.typeAnnotation); }, FunctionDeclaration(node) { if (isInsideClass(node)) return; node.params.forEach((param) => { checkParameter(param); }); if (checkReturnTypes && node.returnType) reportTypeAnnotation(node.returnType.typeAnnotation); }, FunctionExpression(node) { if (isInsideClass(node)) return; node.params.forEach((param) => { checkParameter(param); }); if (checkReturnTypes && node.returnType) reportTypeAnnotation(node.returnType.typeAnnotation); }, ArrowFunctionExpression(node) { if (isInsideClass(node)) return; node.params.forEach((param) => { checkParameter(param); }); if (checkReturnTypes && node.returnType) reportTypeAnnotation(node.returnType.typeAnnotation); } }; } }); //#endregion //#region src/rules/no-redundant-jsx-prop-usage.ts const RULE_NAME$2 = "no-redundant-jsx-prop-usage"; const SKIP = Symbol("SKIP"); /** * Extracts a primitive literal value from a TypeScript AST expression node. * Returns SKIP if the expression is not a primitive literal we can compare. */ const extractPrimitiveLiteral = (node) => { if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text; if (ts.isNumericLiteral(node)) return Number(node.text); if (node.kind === ts.SyntaxKind.TrueKeyword) return true; if (node.kind === ts.SyntaxKind.FalseKeyword) return false; if (node.kind === ts.SyntaxKind.NullKeyword) return null; if (ts.isIdentifier(node) && node.text === "undefined") return; if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(node.operand)) return -Number(node.operand.text); return SKIP; }; /** * If the call expression is React.forwardRef(...) or forwardRef(...), * returns the inner function (the actual component). Otherwise returns undefined. */ const unwrapForwardRef = (callExpr) => { const callee = callExpr.expression; let funcName; if (ts.isIdentifier(callee)) funcName = callee.text; else if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.name)) funcName = callee.name.text; if (funcName === "forwardRef") { const firstArg = callExpr.arguments[0]; if (ts.isArrowFunction(firstArg) || ts.isFunctionExpression(firstArg)) return firstArg; } }; /** * Given a TypeScript declaration node, attempts to find the function-like node * that represents the React component. Returns a Map of prop name -\> default value, * or null if the declaration cannot be analyzed. */ const extractDefaultsFromDeclaration = (decl) => { let functionNode; if (ts.isFunctionDeclaration(decl) || ts.isFunctionExpression(decl) || ts.isArrowFunction(decl)) functionNode = decl; else if (ts.isVariableDeclaration(decl)) { const init = decl.initializer; if (!init) return null; if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) functionNode = init; else if (ts.isCallExpression(init)) { const inner = unwrapForwardRef(init); if (inner) functionNode = inner; } } if (!functionNode) return null; const firstParam = functionNode.parameters[0]; if (!firstParam) return null; const bindingPattern = firstParam.name; if (!ts.isObjectBindingPattern(bindingPattern)) return null; const defaults = /* @__PURE__ */ new Map(); for (const element of bindingPattern.elements) { if (!element.initializer) continue; let propName; if (element.propertyName) { if (ts.isIdentifier(element.propertyName)) propName = element.propertyName.text; } else if (ts.isIdentifier(element.name)) propName = element.name.text; if (!propName) continue; const defaultValue = extractPrimitiveLiteral(element.initializer); if (defaultValue !== SKIP) defaults.set(propName, defaultValue); } return defaults; }; /** * Extracts a primitive value from a JSX attribute value (ESTree side). * Returns SKIP if the value is not a comparable primitive literal. */ const getJSXAttributePrimitiveValue = (attr) => { const { value } = attr; if (value === null) return true; if (value.type === AST_NODE_TYPES.Literal) { const v = value.value; if (typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v === null) return v; return SKIP; } if (value.type === AST_NODE_TYPES.JSXExpressionContainer) { const expr = value.expression; if (expr.type === AST_NODE_TYPES.Literal) { const v = expr.value; if (typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v === null) return v; } if (expr.type === AST_NODE_TYPES.Identifier && expr.name === "undefined") return; if (expr.type === AST_NODE_TYPES.UnaryExpression && expr.operator === "-" && expr.argument.type === AST_NODE_TYPES.Literal && typeof expr.argument.value === "number") return -expr.argument.value; } return SKIP; }; var no_redundant_jsx_prop_usage_default = createEslintRule({ name: RULE_NAME$2, meta: { type: "suggestion", docs: { description: "disallow passing a JSX prop whose value matches the component's destructuring default for that prop", url: "https://github.com/AndreaPontrandolfo/eslint-plugin-fsecond/blob/master/docs/rules/no-redundant-jsx-prop-usage.md", recommended: false, requiresTypeChecking: true }, fixable: "code", schema: [], messages: { noRedundantJsxPropUsage: "Prop \"{{propName}}\" is redundant — it matches the default value ({{defaultValue}})." } }, defaultOptions: [], create(context) { const services = ESLintUtils.getParserServices(context); const checker = services.program.getTypeChecker(); const defaultsCache = /* @__PURE__ */ new Map(); const getDefaultsForSymbol = (symbol) => { if (defaultsCache.has(symbol)) return defaultsCache.get(symbol) ?? null; const { declarations } = symbol; if (!declarations || declarations.length === 0) { defaultsCache.set(symbol, null); return null; } for (const declaration of declarations) { const defaults = extractDefaultsFromDeclaration(declaration); if (defaults !== null) { defaultsCache.set(symbol, defaults); return defaults; } } defaultsCache.set(symbol, null); return null; }; return { JSXOpeningElement(node) { const nameNode = node.name; if (nameNode.type === AST_NODE_TYPES.JSXIdentifier && (nameNode.name.length === 0 || nameNode.name.startsWith(nameNode.name[0].toLowerCase()))) return; const symbol = services.getSymbolAtLocation(nameNode); if (!symbol) return; const defaults = getDefaultsForSymbol(symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol); if (!defaults || defaults.size === 0) return; for (const attr of node.attributes) { if (attr.type !== AST_NODE_TYPES.JSXAttribute) continue; const propNameNode = attr.name; if (propNameNode.type !== AST_NODE_TYPES.JSXIdentifier) continue; const propName = propNameNode.name; if (!defaults.has(propName)) continue; const attrValue = getJSXAttributePrimitiveValue(attr); if (attrValue === SKIP) continue; const defaultValue = defaults.get(propName); if (attrValue === defaultValue) context.report({ node: attr, messageId: "noRedundantJsxPropUsage", data: { propName, defaultValue: String(defaultValue) }, fix(fixer) { const tokenBefore = context.sourceCode.getTokenBefore(attr); const start = tokenBefore ? tokenBefore.range[1] : attr.range[0]; return fixer.removeRange([start, attr.range[1]]); } }); } } }; } }); //#endregion //#region src/rules/prefer-destructured-optionals.ts const RULE_NAME$1 = "prefer-destructured-optionals"; var prefer_destructured_optionals_default = createEslintRule({ name: RULE_NAME$1, meta: { type: "suggestion", docs: { description: "enforce placing optional parameters on a destructured object instead of the function signature itself", recommended: true, url: "https://github.com/AndreaPontrandolfo/eslint-plugin-fsecond/blob/master/docs/rules/prefer-destructured-optionals.md" }, schema: [], messages: { noNonDestructuredOptional: "Convert this optional parameter to a destructured parameter." } }, defaultOptions: [], create(context) { const checkParameters = (params) => { let isParamObjectInTheMiddle = false; params.forEach((param) => { if (param.type === AST_NODE_TYPES.AssignmentPattern && param.left.type !== AST_NODE_TYPES.ObjectPattern || param.type === AST_NODE_TYPES.Identifier && param.optional) context.report({ node: param, messageId: "noNonDestructuredOptional" }); if (isParamObjectInTheMiddle && param.type !== AST_NODE_TYPES.ObjectPattern) context.report({ node: param, messageId: "noNonDestructuredOptional" }); if (param.type === AST_NODE_TYPES.ObjectPattern || param.type === AST_NODE_TYPES.AssignmentPattern && param.left.type === AST_NODE_TYPES.ObjectPattern) isParamObjectInTheMiddle = true; }); }; return { FunctionDeclaration(node) { checkParameters(node.params); }, FunctionExpression(node) { checkParameters(node.params); }, ArrowFunctionExpression(node) { checkParameters(node.params); } }; } }); //#endregion //#region src/rules/valid-event-listener.ts const RULE_NAME = "valid-event-listener"; /** * Helper function: Check if a node is a method call to addEventListener or removeEventListener. */ const isEventListenerMethodCall = (node, methodName) => { let currentNode = node; if (currentNode instanceof Object && "type" in currentNode && currentNode.type === AST_NODE_TYPES.ChainExpression) currentNode = currentNode.expression; if (!(currentNode instanceof Object) || !("type" in currentNode) || currentNode.type !== AST_NODE_TYPES.CallExpression || !("callee" in currentNode)) return false; let { callee } = currentNode; if (callee instanceof Object && callee.type === AST_NODE_TYPES.ChainExpression) callee = callee.expression; return callee instanceof Object && callee.type === AST_NODE_TYPES.MemberExpression && callee.property instanceof Object && callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === methodName; }; /** * Helper function: Check if node is an addEventListener call. */ const isAddEventListenerCall = (node) => { return isEventListenerMethodCall(node, "addEventListener"); }; /** * Helper function: Check if node is a removeEventListener call. */ const isRemoveEventListenerCall = (node) => { return isEventListenerMethodCall(node, "removeEventListener"); }; /** * Helper function: Check if a call expression is useEffect or useLayoutEffect. */ const isUseEffectOrUseLayoutEffectCall = (node) => { const { callee } = node; if (callee.type === AST_NODE_TYPES.Identifier) return callee.name === "useEffect" || callee.name === "useLayoutEffect"; if (callee.type === AST_NODE_TYPES.MemberExpression && !callee.computed) return callee.property.type === AST_NODE_TYPES.Identifier && (callee.property.name === "useEffect" || callee.property.name === "useLayoutEffect"); return false; }; /** * Helper function: Check if an expression statement contains a conditional addEventListener. */ const isConditionalAddEventListener = (node) => { if (node.type !== AST_NODE_TYPES.IfStatement) return false; if (node.consequent.type === AST_NODE_TYPES.BlockStatement) { for (const statement of node.consequent.body) if (statement.type === AST_NODE_TYPES.ExpressionStatement && isAddEventListenerCall(statement.expression)) return true; } if (node.consequent.type === AST_NODE_TYPES.ExpressionStatement && isAddEventListenerCall(node.consequent.expression)) return true; if (node.alternate?.type === AST_NODE_TYPES.BlockStatement) { for (const statement of node.alternate.body) if (statement.type === AST_NODE_TYPES.ExpressionStatement && isAddEventListenerCall(statement.expression)) return true; } return node.alternate?.type === AST_NODE_TYPES.ExpressionStatement && isAddEventListenerCall(node.alternate.expression); }; /** * Helper function: Check if expression has conditional addEventListener (logical or ternary). */ const isConditionalExpression = (expression) => { if (expression.type === AST_NODE_TYPES.LogicalExpression && expression.operator === "&&" && isAddEventListenerCall(expression.right)) return true; if (expression.type === AST_NODE_TYPES.ConditionalExpression) { if (isAddEventListenerCall(expression.consequent)) return true; if (isAddEventListenerCall(expression.alternate)) return true; } return false; }; /** * Helper function: Search block for addEventListener, return true if found. */ const findAddEventListenerInBlock = (statements) => { for (const statement of statements) if (statement.type === AST_NODE_TYPES.ExpressionStatement) { if (isAddEventListenerCall(statement.expression)) return true; if (isConditionalExpression(statement.expression)) return true; } return false; }; /** * Helper function: Search block for removeEventListener, return true if found. */ const findRemoveEventListenerInBlock = (statements) => { for (const statement of statements) if (statement.type === AST_NODE_TYPES.ExpressionStatement && isRemoveEventListenerCall(statement.expression)) return true; return false; }; /** * Helper function: Search cleanup function (return statement) for removeEventListener. */ const findRemoveEventListenerInCleanup = (statements) => { for (const statement of statements) { if (statement.type !== AST_NODE_TYPES.ReturnStatement) continue; const cleanupFunction = statement.argument; if (cleanupFunction && (cleanupFunction.type === AST_NODE_TYPES.ArrowFunctionExpression || cleanupFunction.type === AST_NODE_TYPES.FunctionExpression) && cleanupFunction.body?.type === AST_NODE_TYPES.BlockStatement && cleanupFunction.body.body && findRemoveEventListenerInBlock(cleanupFunction.body.body)) return true; if (cleanupFunction?.type === AST_NODE_TYPES.ArrowFunctionExpression && cleanupFunction.body && isRemoveEventListenerCall(cleanupFunction.body)) return true; } return false; }; /** * Helper function: Check if there's a return statement with a cleanup function. */ const hasReturnStatement = (statements) => { for (const statement of statements) if (statement.type === AST_NODE_TYPES.ReturnStatement && statement.argument && (statement.argument.type === AST_NODE_TYPES.ArrowFunctionExpression || statement.argument.type === AST_NODE_TYPES.FunctionExpression)) return true; return false; }; /** * Helper function: Check if useEffect body has conditional addEventListener. */ const hasConditionalAddEventListener = (statements) => { for (const statement of statements) { if (isConditionalAddEventListener(statement)) return true; if (statement.type === AST_NODE_TYPES.ExpressionStatement && isConditionalExpression(statement.expression)) return true; } return false; }; /** * Helper function: Unwrap ChainExpression to get the actual CallExpression. */ const unwrapToCallExpression = (node) => { let currentNode = node; if (currentNode instanceof Object && "type" in currentNode && currentNode.type === AST_NODE_TYPES.ChainExpression) currentNode = currentNode.expression; if (!(currentNode instanceof Object) || !("type" in currentNode) || currentNode.type !== AST_NODE_TYPES.CallExpression) return null; return currentNode; }; /** * Helper function: Check if addEventListener call has \{ once: true \} option. */ const hasOnceOption = (call) => { if (!isEventListenerMethodCall(call, "addEventListener")) return false; const callExpression = unwrapToCallExpression(call); if (!callExpression) return false; const optionsArgument = callExpression.arguments[2]; if (optionsArgument?.type === AST_NODE_TYPES.ObjectExpression) { for (const property of optionsArgument.properties) if (property.type === AST_NODE_TYPES.Property && property.key.type === AST_NODE_TYPES.Identifier && property.key.name === "once" && property.value.type === AST_NODE_TYPES.Literal && property.value.value === true) return true; } return false; }; /** * Helper function: Check if there's any addEventListener in block that is not once. * Returns true if at least one addEventListener does NOT have \{ once: true \}. */ const hasNonOnceAddEventListenerInBlock = (statements) => { for (const statement of statements) if (statement.type === AST_NODE_TYPES.ExpressionStatement && isEventListenerMethodCall(statement.expression, "addEventListener") && !hasOnceOption(statement.expression)) return true; return false; }; var valid_event_listener_default = createEslintRule({ name: RULE_NAME, meta: { type: "problem", docs: { description: "enforces best practices around addEventListener method in React components.", url: "https://github.com/AndreaPontrandolfo/eslint-plugin-fsecond/blob/master/docs/rules/valid-event-listener.md", recommended: true }, schema: [{ type: "object", additionalProperties: false, properties: { requireUseEventListenerHook: { description: "Require the use of a useEventListener hook", type: "boolean" } } }], defaultOptions: [{ requireUseEventListenerHook: true }], messages: { "required-cleanup": "Missing a cleanup function for the addEventListener in React useEffect.", "required-remove-eventListener": "Missing a matching removeEventListener in the React useEffect cleanup.", "no-conditional-addeventlistener": "Don't wrap addEventListener in a condition in React components.", "require-use-event-listener-hook": "Use a useEventListener hook from a React hooks library instead of manually adding and removing event listeners." } }, defaultOptions: [{ requireUseEventListenerHook: true }], create(context) { const { requireUseEventListenerHook } = { requireUseEventListenerHook: true, ...context.options[0] ?? {} }; return { ExpressionStatement(node) { const expression = node?.expression; if (expression.type !== AST_NODE_TYPES.CallExpression) return; if (!isUseEffectOrUseLayoutEffectCall(expression)) return; const firstArgument = expression.arguments[0]; const isFunctionExpression = firstArgument?.type === AST_NODE_TYPES.ArrowFunctionExpression || firstArgument?.type === AST_NODE_TYPES.FunctionExpression; const useEffectBodyInternalItems = expression?.arguments && expression.arguments.length > 0 && isFunctionExpression && firstArgument.body?.type === AST_NODE_TYPES.BlockStatement && firstArgument.body.body; if (!useEffectBodyInternalItems || useEffectBodyInternalItems.length === 0) return; if (hasConditionalAddEventListener(useEffectBodyInternalItems)) { context.report({ node, messageId: "no-conditional-addeventlistener" }); return; } if (!findAddEventListenerInBlock(useEffectBodyInternalItems)) return; if (requireUseEventListenerHook) { context.report({ node, messageId: "require-use-event-listener-hook" }); return; } const hasReturnStmt = hasReturnStatement(useEffectBodyInternalItems); if (!hasNonOnceAddEventListenerInBlock(useEffectBodyInternalItems)) return; if (!findRemoveEventListenerInCleanup(useEffectBodyInternalItems)) if (hasReturnStmt) context.report({ node, messageId: "required-remove-eventListener" }); else context.report({ node, messageId: "required-cleanup" }); } }; } }); //#endregion //#region src/index.ts const eslintPluginShortName = "fsecond"; const plugin = { meta: { name, version }, configs: {}, rules: { "prefer-destructured-optionals": prefer_destructured_optionals_default, "valid-event-listener": valid_event_listener_default, "no-inline-interfaces": no_inline_interfaces_default, "no-redundant-jsx-prop-usage": no_redundant_jsx_prop_usage_default } }; plugin.configs = { recommended: [{ plugins: { fsecond: plugin }, rules: { [`${eslintPluginShortName}/prefer-destructured-optionals`]: 2, [`${eslintPluginShortName}/valid-event-listener`]: 2, [`${eslintPluginShortName}/no-inline-interfaces`]: [2, { checkGenericTypes: false, checkReturnTypes: true }] } }], recommendedTypeChecked: [{ plugins: { fsecond: plugin }, rules: { [`${eslintPluginShortName}/prefer-destructured-optionals`]: 2, [`${eslintPluginShortName}/valid-event-listener`]: 2, [`${eslintPluginShortName}/no-inline-interfaces`]: [2, { checkGenericTypes: false, checkReturnTypes: true }], [`${eslintPluginShortName}/no-redundant-jsx-prop-usage`]: 2 } }] }; //#endregion export { plugin as default }; //# sourceMappingURL=index.mjs.map