eslint-plugin-fsecond
Version:
ESLint plugin with misc rules. Typescript and React related.
1 lines • 55 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","names":["RULE_NAME","RULE_NAME","RULE_NAME","packageJson.name","packageJson.version","preferDestructuredOptionals","validEventListener","noInlineInterfaces","noRedundantJsxPropUsage"],"sources":["../package.json","../src/utils.ts","../src/rules/no-inline-interfaces.ts","../src/rules/no-redundant-jsx-prop-usage.ts","../src/rules/prefer-destructured-optionals.ts","../src/rules/valid-event-listener.ts","../src/index.ts"],"sourcesContent":["","import { ESLintUtils } from \"@typescript-eslint/utils\";\nimport type { FsecondDocs } from \"./types\";\n\nexport const createEslintRule = ESLintUtils.RuleCreator<FsecondDocs>(\n (ruleName) => ruleName,\n);\n","/* eslint-disable @typescript-eslint/switch-exhaustiveness-check */\n/* eslint-disable fsecond/prefer-destructured-optionals */\nimport {\n AST_NODE_TYPES,\n ASTUtils,\n type TSESTree,\n} from \"@typescript-eslint/utils\";\nimport { createEslintRule } from \"../utils\";\n\nconst RULE_NAME = \"no-inline-interfaces\";\n\nconst isStatementParent = ASTUtils.isNodeOfTypes([\n AST_NODE_TYPES.Program,\n AST_NODE_TYPES.BlockStatement,\n AST_NODE_TYPES.TSModuleBlock,\n] as const);\n\nconst findStatementAncestor = (node: TSESTree.Node): TSESTree.Node | null => {\n if (!node.parent) {\n return null;\n }\n if (isStatementParent(node.parent)) {\n return node;\n }\n\n return findStatementAncestor(node.parent);\n};\n\ntype MessageIds = \"noInlineInterfaces\";\ntype Options = [\n {\n checkGenericTypes?: boolean;\n checkReturnTypes?: boolean;\n }?,\n];\n\n/**\n * Recursively find all TSTypeLiteral nodes in a type annotation.\n * Returns all inline object type literals that should be reported.\n */\nconst findTypeLiterals = (\n type: TSESTree.TypeNode | undefined,\n results: TSESTree.TSTypeLiteral[] = [],\n checkGenericTypes = false,\n): TSESTree.TSTypeLiteral[] => {\n if (!type) {\n return results;\n }\n\n switch (type.type) {\n case AST_NODE_TYPES.TSTypeLiteral: {\n results.push(type);\n // Also check for nested type literals inside the members\n type.members.forEach((member) => {\n if (\n member.type === AST_NODE_TYPES.TSPropertySignature &&\n member.typeAnnotation\n ) {\n findTypeLiterals(\n member.typeAnnotation.typeAnnotation,\n results,\n checkGenericTypes,\n );\n }\n });\n\n return results;\n }\n\n case AST_NODE_TYPES.TSUnionType:\n case AST_NODE_TYPES.TSIntersectionType: {\n for (const typeNode of type.types) {\n findTypeLiterals(typeNode, results, checkGenericTypes);\n }\n break;\n }\n\n case AST_NODE_TYPES.TSTypeReference: {\n // Only check generic type arguments if checkGenericTypes is true\n if (checkGenericTypes && type.typeArguments) {\n type.typeArguments.params.forEach((param) => {\n findTypeLiterals(param, results, checkGenericTypes);\n });\n }\n break;\n }\n\n case AST_NODE_TYPES.TSArrayType: {\n findTypeLiterals(type.elementType, results, checkGenericTypes);\n break;\n }\n\n case AST_NODE_TYPES.TSTupleType: {\n type.elementTypes.forEach((elementType) => {\n findTypeLiterals(elementType, results, checkGenericTypes);\n });\n break;\n }\n\n case AST_NODE_TYPES.TSOptionalType: {\n findTypeLiterals(type.typeAnnotation, results, checkGenericTypes);\n break;\n }\n\n case AST_NODE_TYPES.TSRestType: {\n findTypeLiterals(type.typeAnnotation, results, checkGenericTypes);\n break;\n }\n\n case AST_NODE_TYPES.TSTypeOperator: {\n findTypeLiterals(type.typeAnnotation, results, checkGenericTypes);\n break;\n }\n\n case AST_NODE_TYPES.TSIndexedAccessType: {\n findTypeLiterals(type.objectType, results, checkGenericTypes);\n findTypeLiterals(type.indexType, results, checkGenericTypes);\n break;\n }\n\n case AST_NODE_TYPES.TSConditionalType: {\n // Check the true and false branches of conditional types\n findTypeLiterals(type.trueType, results, checkGenericTypes);\n findTypeLiterals(type.falseType, results, checkGenericTypes);\n findTypeLiterals(type.checkType, results, checkGenericTypes);\n findTypeLiterals(type.extendsType, results, checkGenericTypes);\n break;\n }\n case AST_NODE_TYPES.TSConstructorType:\n case AST_NODE_TYPES.TSFunctionType: {\n // Check parameter types and return type\n type.params.forEach((param) => {\n if (param.type === AST_NODE_TYPES.TSParameterProperty) {\n findTypeLiterals(\n param.parameter.typeAnnotation?.typeAnnotation,\n results,\n checkGenericTypes,\n );\n } else if (param.typeAnnotation) {\n findTypeLiterals(\n param.typeAnnotation.typeAnnotation,\n results,\n checkGenericTypes,\n );\n }\n });\n findTypeLiterals(\n type.returnType?.typeAnnotation,\n results,\n checkGenericTypes,\n );\n break;\n }\n case AST_NODE_TYPES.TSImportType: {\n // Check type arguments if present\n if (type.typeArguments) {\n type.typeArguments.params.forEach((param) => {\n findTypeLiterals(param, results, checkGenericTypes);\n });\n }\n break;\n }\n case AST_NODE_TYPES.TSMappedType: {\n // Check the mapped type's type annotation\n findTypeLiterals(type.typeAnnotation, results, checkGenericTypes);\n if (type.nameType) {\n findTypeLiterals(type.nameType, results, checkGenericTypes);\n }\n break;\n }\n case AST_NODE_TYPES.TSNamedTupleMember: {\n // Check the element type of the named tuple member\n findTypeLiterals(type.elementType, results, checkGenericTypes);\n break;\n }\n case AST_NODE_TYPES.TSTypePredicate: {\n // Check the parameter name type and type annotation\n if (type.typeAnnotation) {\n findTypeLiterals(\n type.typeAnnotation.typeAnnotation,\n results,\n checkGenericTypes,\n );\n }\n break;\n }\n default: {\n // Ignore other type nodes (primitives, etc.)\n break;\n }\n }\n\n return results;\n};\n\nconst isClassNode = ASTUtils.isNodeOfTypes([\n AST_NODE_TYPES.ClassDeclaration,\n AST_NODE_TYPES.ClassExpression,\n] as const);\n\n/**\n * Check if a node is inside a class.\n */\nconst isInsideClass = (node: TSESTree.Node): boolean => {\n if (!node.parent) {\n return false;\n }\n if (isClassNode(node.parent)) {\n return true;\n }\n\n return isInsideClass(node.parent);\n};\n\nexport default createEslintRule<Options, MessageIds>({\n name: RULE_NAME,\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"disallow inline object type literals in variable and function annotations; extract to a named interface or type alias.\",\n url: \"https://github.com/AndreaPontrandolfo/eslint-plugin-fsecond/blob/master/docs/rules/no-inline-interfaces.md\",\n recommended: true,\n },\n fixable: \"code\",\n schema: [\n {\n type: \"object\",\n properties: {\n checkGenericTypes: {\n type: \"boolean\",\n description:\n \"Check inline object types within generic type arguments (e.g., Array<{ a: string }>)\",\n },\n checkReturnTypes: {\n type: \"boolean\",\n description:\n \"Check inline object types in function return type annotations\",\n },\n },\n additionalProperties: false,\n },\n ],\n defaultOptions: [\n {\n checkGenericTypes: false,\n checkReturnTypes: false,\n },\n ],\n messages: {\n noInlineInterfaces:\n \"Extract this inline object type into a named interface or type alias and reference it here.\",\n },\n },\n defaultOptions: [\n {\n checkGenericTypes: false,\n checkReturnTypes: false,\n },\n ],\n create(context) {\n const options = context.options[0] ?? {};\n const checkGenericTypes = options.checkGenericTypes ?? false;\n const checkReturnTypes = options.checkReturnTypes ?? false;\n let interfaceCounter = 0;\n\n /**\n * Report all inline object type literals found in a type annotation.\n */\n const reportTypeAnnotation = (\n typeAnnotation: TSESTree.TypeNode | undefined,\n ) => {\n const literals = findTypeLiterals(typeAnnotation, [], checkGenericTypes);\n\n for (const literal of literals) {\n interfaceCounter = interfaceCounter + 1;\n const interfaceName =\n interfaceCounter === 1\n ? \"InlineInterface\"\n : `InlineInterface${String(interfaceCounter)}`;\n\n context.report({\n node: literal,\n messageId: \"noInlineInterfaces\",\n fix(fixer) {\n const statementNode = findStatementAncestor(literal);\n\n if (!statementNode) {\n return null;\n }\n\n const { sourceCode } = context;\n const openBrace = sourceCode.getFirstToken(literal);\n const closeBrace = sourceCode.getLastToken(literal);\n\n if (!openBrace || !closeBrace) {\n return null;\n }\n\n const bodyText = sourceCode\n .getText()\n .slice(openBrace.range[1], closeBrace.range[0]);\n\n return [\n fixer.insertTextBefore(\n statementNode,\n `interface ${interfaceName} {${bodyText}}\\n`,\n ),\n fixer.replaceText(literal, interfaceName),\n ];\n },\n });\n }\n };\n\n /**\n * Check a parameter node for type annotations\n * Handles both direct parameters and parameters with default values (AssignmentPattern).\n */\n const checkParameter = (param: TSESTree.Parameter) => {\n let nodeToCheck: TSESTree.Node = param;\n\n // If parameter has a default value, it's wrapped in AssignmentPattern\n // The type annotation is on the 'left' side\n if (param.type === AST_NODE_TYPES.AssignmentPattern) {\n nodeToCheck = param.left;\n }\n\n // Check for node types that can have type annotations\n if (\n (nodeToCheck.type === AST_NODE_TYPES.Identifier ||\n nodeToCheck.type === AST_NODE_TYPES.ArrayPattern ||\n nodeToCheck.type === AST_NODE_TYPES.ObjectPattern ||\n nodeToCheck.type === AST_NODE_TYPES.RestElement) &&\n nodeToCheck.typeAnnotation\n ) {\n reportTypeAnnotation(nodeToCheck.typeAnnotation.typeAnnotation);\n }\n };\n\n return {\n VariableDeclarator(node) {\n // Skip if inside a class\n if (isInsideClass(node)) {\n return;\n }\n\n // Check variable type annotation\n if (node.id.typeAnnotation) {\n reportTypeAnnotation(node.id.typeAnnotation.typeAnnotation);\n }\n },\n\n FunctionDeclaration(node) {\n // Skip if inside a class\n if (isInsideClass(node)) {\n return;\n }\n\n // Check each parameter's type annotation\n node.params.forEach((param) => {\n checkParameter(param);\n });\n\n // Check return type annotation if enabled\n if (checkReturnTypes && node.returnType) {\n reportTypeAnnotation(node.returnType.typeAnnotation);\n }\n },\n\n FunctionExpression(node) {\n // Skip if inside a class\n if (isInsideClass(node)) {\n return;\n }\n\n // Check each parameter's type annotation\n node.params.forEach((param) => {\n checkParameter(param);\n });\n\n // Check return type annotation if enabled\n if (checkReturnTypes && node.returnType) {\n reportTypeAnnotation(node.returnType.typeAnnotation);\n }\n },\n\n ArrowFunctionExpression(node) {\n // Skip if inside a class\n if (isInsideClass(node)) {\n return;\n }\n\n // Check each parameter's type annotation\n node.params.forEach((param) => {\n checkParameter(param);\n });\n\n // Check return type annotation if enabled\n if (checkReturnTypes && node.returnType) {\n reportTypeAnnotation(node.returnType.typeAnnotation);\n }\n },\n };\n },\n});\n","import * as ts from \"typescript\";\nimport {\n AST_NODE_TYPES,\n ESLintUtils,\n type TSESTree,\n} from \"@typescript-eslint/utils\";\nimport { createEslintRule } from \"../utils\";\n\nconst RULE_NAME = \"no-redundant-jsx-prop-usage\";\n\ntype MessageIds = \"noRedundantJsxPropUsage\";\ntype Options = [];\n\n// Sentinel value to distinguish \"not a comparable primitive\" from a real value\nconst SKIP = Symbol(\"SKIP\");\n\ntype PrimitiveValue = string | number | boolean | null | undefined;\n\n/**\n * Extracts a primitive literal value from a TypeScript AST expression node.\n * Returns SKIP if the expression is not a primitive literal we can compare.\n */\nconst extractPrimitiveLiteral = (\n node: ts.Expression,\n): PrimitiveValue | typeof SKIP => {\n if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {\n return node.text;\n }\n if (ts.isNumericLiteral(node)) {\n return Number(node.text);\n }\n if (node.kind === ts.SyntaxKind.TrueKeyword) {\n return true;\n }\n if (node.kind === ts.SyntaxKind.FalseKeyword) {\n return false;\n }\n if (node.kind === ts.SyntaxKind.NullKeyword) {\n return null;\n }\n if (ts.isIdentifier(node) && node.text === \"undefined\") {\n return undefined;\n }\n // Negative numbers: -42\n if (\n ts.isPrefixUnaryExpression(node) &&\n node.operator === ts.SyntaxKind.MinusToken &&\n ts.isNumericLiteral(node.operand)\n ) {\n return -Number(node.operand.text);\n }\n\n return SKIP;\n};\n\n/**\n * If the call expression is React.forwardRef(...) or forwardRef(...),\n * returns the inner function (the actual component). Otherwise returns undefined.\n */\nconst unwrapForwardRef = (\n callExpr: ts.CallExpression,\n): ts.ArrowFunction | ts.FunctionExpression | undefined => {\n const callee = callExpr.expression;\n let funcName: string | undefined;\n\n if (ts.isIdentifier(callee)) {\n funcName = callee.text;\n } else if (\n ts.isPropertyAccessExpression(callee) &&\n ts.isIdentifier(callee.name)\n ) {\n funcName = callee.name.text;\n }\n\n if (funcName === \"forwardRef\") {\n const firstArg = callExpr.arguments[0];\n\n if (ts.isArrowFunction(firstArg) || ts.isFunctionExpression(firstArg)) {\n return firstArg;\n }\n }\n\n return undefined;\n};\n\n/**\n * Given a TypeScript declaration node, attempts to find the function-like node\n * that represents the React component. Returns a Map of prop name -\\> default value,\n * or null if the declaration cannot be analyzed.\n */\nconst extractDefaultsFromDeclaration = (\n decl: ts.Declaration,\n): Map<string, PrimitiveValue> | null => {\n let functionNode: ts.FunctionLikeDeclaration | undefined;\n\n if (\n ts.isFunctionDeclaration(decl) ||\n ts.isFunctionExpression(decl) ||\n ts.isArrowFunction(decl)\n ) {\n functionNode = decl;\n } else if (ts.isVariableDeclaration(decl)) {\n const init = decl.initializer;\n\n if (!init) {\n return null;\n }\n if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) {\n functionNode = init;\n } else if (ts.isCallExpression(init)) {\n // Handle React.forwardRef(...)\n const inner = unwrapForwardRef(init);\n\n if (inner) {\n functionNode = inner;\n }\n }\n }\n\n if (!functionNode) {\n return null;\n }\n\n const firstParam = functionNode.parameters[0];\n\n // Parameters array can be empty for zero-argument functions\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!firstParam) {\n return null;\n }\n\n const bindingPattern = firstParam.name;\n\n if (!ts.isObjectBindingPattern(bindingPattern)) {\n return null;\n }\n\n const defaults = new Map<string, PrimitiveValue>();\n\n for (const element of bindingPattern.elements) {\n if (!element.initializer) {\n continue;\n }\n\n // The prop name is either the property name (for renamed bindings: { foo: bar = \"x\" })\n // or the element name itself (for simple bindings: { foo = \"x\" })\n let propName: string | undefined;\n\n if (element.propertyName) {\n if (ts.isIdentifier(element.propertyName)) {\n propName = element.propertyName.text;\n }\n } else if (ts.isIdentifier(element.name)) {\n propName = element.name.text;\n }\n\n if (!propName) {\n continue;\n }\n\n const defaultValue = extractPrimitiveLiteral(element.initializer);\n\n if (defaultValue !== SKIP) {\n defaults.set(propName, defaultValue);\n }\n }\n\n return defaults;\n};\n\n/**\n * Extracts a primitive value from a JSX attribute value (ESTree side).\n * Returns SKIP if the value is not a comparable primitive literal.\n */\nconst getJSXAttributePrimitiveValue = (\n attr: TSESTree.JSXAttribute,\n): PrimitiveValue | typeof SKIP => {\n const { value } = attr;\n\n // Boolean shorthand: <Button disabled /> means disabled={true}\n if (value === null) {\n return true;\n }\n\n // String literal: <Button variant=\"primary\" />\n if (value.type === AST_NODE_TYPES.Literal) {\n const v = value.value;\n\n if (\n typeof v === \"string\" ||\n typeof v === \"number\" ||\n typeof v === \"boolean\" ||\n v === null\n ) {\n return v;\n }\n\n return SKIP;\n }\n\n // JSX expression container: <Button count={42} />\n if (value.type === AST_NODE_TYPES.JSXExpressionContainer) {\n const expr = value.expression;\n\n if (expr.type === AST_NODE_TYPES.Literal) {\n const v = expr.value;\n\n if (\n typeof v === \"string\" ||\n typeof v === \"number\" ||\n typeof v === \"boolean\" ||\n v === null\n ) {\n return v;\n }\n }\n\n // {undefined}\n if (expr.type === AST_NODE_TYPES.Identifier && expr.name === \"undefined\") {\n return undefined;\n }\n\n // Negative numbers: {-42}\n if (\n expr.type === AST_NODE_TYPES.UnaryExpression &&\n expr.operator === \"-\" &&\n expr.argument.type === AST_NODE_TYPES.Literal &&\n typeof expr.argument.value === \"number\"\n ) {\n return -expr.argument.value;\n }\n }\n\n return SKIP;\n};\n\nexport default createEslintRule<Options, MessageIds>({\n name: RULE_NAME,\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"disallow passing a JSX prop whose value matches the component's destructuring default for that prop\",\n url: \"https://github.com/AndreaPontrandolfo/eslint-plugin-fsecond/blob/master/docs/rules/no-redundant-jsx-prop-usage.md\",\n recommended: false,\n requiresTypeChecking: true,\n },\n fixable: \"code\",\n schema: [],\n messages: {\n noRedundantJsxPropUsage:\n 'Prop \"{{propName}}\" is redundant — it matches the default value ({{defaultValue}}).',\n },\n },\n defaultOptions: [],\n create(context) {\n const services = ESLintUtils.getParserServices(context);\n const checker = services.program.getTypeChecker();\n\n // Cache defaults per resolved symbol to avoid redundant work within a file\n const defaultsCache = new Map<\n ts.Symbol,\n Map<string, PrimitiveValue> | null\n >();\n\n const getDefaultsForSymbol = (\n symbol: ts.Symbol,\n ): Map<string, PrimitiveValue> | null => {\n if (defaultsCache.has(symbol)) {\n return defaultsCache.get(symbol) ?? null;\n }\n\n const { declarations } = symbol;\n\n if (!declarations || declarations.length === 0) {\n defaultsCache.set(symbol, null);\n\n return null;\n }\n\n for (const declaration of declarations) {\n const defaults = extractDefaultsFromDeclaration(declaration);\n\n if (defaults !== null) {\n defaultsCache.set(symbol, defaults);\n\n return defaults;\n }\n }\n\n defaultsCache.set(symbol, null);\n\n return null;\n };\n\n return {\n JSXOpeningElement(node) {\n // Skip intrinsic HTML elements (lowercase names like \"div\", \"span\")\n const nameNode = node.name;\n\n // Skip intrinsic HTML elements: lowercase or empty names\n if (\n nameNode.type === AST_NODE_TYPES.JSXIdentifier &&\n (nameNode.name.length === 0 ||\n nameNode.name.startsWith(nameNode.name[0].toLowerCase()))\n ) {\n return;\n }\n\n // Resolve the component symbol via type checker\n const symbol = services.getSymbolAtLocation(nameNode);\n\n if (!symbol) {\n return;\n }\n\n // Follow import aliases to reach the original declaration\n const resolvedSymbol =\n symbol.flags & ts.SymbolFlags.Alias\n ? checker.getAliasedSymbol(symbol)\n : symbol;\n\n const defaults = getDefaultsForSymbol(resolvedSymbol);\n\n if (!defaults || defaults.size === 0) {\n return;\n }\n\n // Check each JSX attribute against the defaults\n for (const attr of node.attributes) {\n if (attr.type !== AST_NODE_TYPES.JSXAttribute) {\n continue;\n }\n\n const propNameNode = attr.name;\n\n if (propNameNode.type !== AST_NODE_TYPES.JSXIdentifier) {\n continue;\n }\n const propName = propNameNode.name;\n\n if (!defaults.has(propName)) {\n continue;\n }\n\n const attrValue = getJSXAttributePrimitiveValue(attr);\n\n if (attrValue === SKIP) {\n continue;\n }\n\n const defaultValue = defaults.get(propName);\n\n if (attrValue === defaultValue) {\n context.report({\n node: attr,\n messageId: \"noRedundantJsxPropUsage\",\n data: {\n propName,\n defaultValue: String(defaultValue),\n },\n fix(fixer) {\n const tokenBefore = context.sourceCode.getTokenBefore(attr);\n\n // Remove the attribute along with the preceding whitespace/token gap\n const start = tokenBefore\n ? tokenBefore.range[1]\n : attr.range[0];\n\n return fixer.removeRange([start, attr.range[1]]);\n },\n });\n }\n }\n },\n };\n },\n});\n","import { AST_NODE_TYPES, type TSESTree } from \"@typescript-eslint/utils\";\nimport { createEslintRule } from \"../utils\";\n\nconst RULE_NAME = \"prefer-destructured-optionals\";\n\ntype MessageIds = \"noNonDestructuredOptional\";\ntype Options = [];\n\nexport default createEslintRule<Options, MessageIds>({\n name: RULE_NAME,\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"enforce placing optional parameters on a destructured object instead of the function signature itself\",\n recommended: true,\n url: \"https://github.com/AndreaPontrandolfo/eslint-plugin-fsecond/blob/master/docs/rules/prefer-destructured-optionals.md\",\n },\n schema: [],\n messages: {\n noNonDestructuredOptional:\n \"Convert this optional parameter to a destructured parameter.\",\n },\n },\n defaultOptions: [],\n create(context) {\n const checkParameters = (params: TSESTree.Parameter[]) => {\n let isParamObjectInTheMiddle = false;\n\n params.forEach((param) => {\n if (\n (param.type === AST_NODE_TYPES.AssignmentPattern &&\n param.left.type !== AST_NODE_TYPES.ObjectPattern) ||\n (param.type === AST_NODE_TYPES.Identifier && param.optional)\n ) {\n context.report({\n node: param,\n messageId: \"noNonDestructuredOptional\",\n });\n }\n\n if (\n isParamObjectInTheMiddle &&\n param.type !== AST_NODE_TYPES.ObjectPattern\n ) {\n context.report({\n node: param,\n messageId: \"noNonDestructuredOptional\",\n });\n }\n\n if (\n param.type === AST_NODE_TYPES.ObjectPattern ||\n (param.type === AST_NODE_TYPES.AssignmentPattern &&\n param.left.type === AST_NODE_TYPES.ObjectPattern)\n ) {\n isParamObjectInTheMiddle = true;\n }\n });\n };\n\n return {\n FunctionDeclaration(node) {\n checkParameters(node.params);\n },\n FunctionExpression(node) {\n checkParameters(node.params);\n },\n ArrowFunctionExpression(node) {\n checkParameters(node.params);\n },\n };\n },\n});\n","/* eslint-disable @typescript-eslint/no-unnecessary-condition */\nimport { AST_NODE_TYPES, type TSESTree } from \"@typescript-eslint/utils\";\nimport { createEslintRule } from \"../utils\";\n\nconst RULE_NAME = \"valid-event-listener\";\n\ntype MessageIds =\n | \"required-cleanup\"\n | \"required-remove-eventListener\"\n | \"no-conditional-addeventlistener\"\n | \"require-use-event-listener-hook\";\ntype Options = [\n {\n requireUseEventListenerHook?: boolean;\n }?,\n];\n\n/**\n * Helper function: Check if a node is a method call to addEventListener or removeEventListener.\n */\nconst isEventListenerMethodCall = (\n node: unknown,\n methodName: \"addEventListener\" | \"removeEventListener\",\n): node is TSESTree.CallExpression => {\n // Handle ChainExpression wrapping the entire CallExpression\n let currentNode = node;\n\n if (\n currentNode instanceof Object &&\n \"type\" in currentNode &&\n currentNode.type === AST_NODE_TYPES.ChainExpression\n ) {\n currentNode = (currentNode as TSESTree.ChainExpression).expression;\n }\n\n // Validate it's a CallExpression\n if (\n !(currentNode instanceof Object) ||\n !(\"type\" in currentNode) ||\n currentNode.type !== AST_NODE_TYPES.CallExpression ||\n !(\"callee\" in currentNode)\n ) {\n return false;\n }\n\n const callExpression = currentNode as TSESTree.CallExpression;\n\n // Handle ChainExpression wrapping the callee MemberExpression\n let { callee } = callExpression;\n\n if (\n callee instanceof Object &&\n callee.type === AST_NODE_TYPES.ChainExpression\n ) {\n callee = callee.expression;\n }\n\n // Check if callee is a MemberExpression with the correct method name\n return (\n callee instanceof Object &&\n callee.type === AST_NODE_TYPES.MemberExpression &&\n callee.property instanceof Object &&\n callee.property.type === AST_NODE_TYPES.Identifier &&\n callee.property.name === methodName\n );\n};\n\n/**\n * Helper function: Check if node is an addEventListener call.\n */\nconst isAddEventListenerCall = (node: unknown): boolean => {\n return isEventListenerMethodCall(node, \"addEventListener\");\n};\n\n/**\n * Helper function: Check if node is a removeEventListener call.\n */\nconst isRemoveEventListenerCall = (node: unknown): boolean => {\n return isEventListenerMethodCall(node, \"removeEventListener\");\n};\n\n/**\n * Helper function: Check if a call expression is useEffect or useLayoutEffect.\n */\nconst isUseEffectOrUseLayoutEffectCall = (\n node: TSESTree.CallExpression,\n): boolean => {\n const { callee } = node;\n\n // Direct call: useEffect() or useLayoutEffect()\n if (callee.type === AST_NODE_TYPES.Identifier) {\n return callee.name === \"useEffect\" || callee.name === \"useLayoutEffect\";\n }\n\n // Member expression call: React.useEffect() or React.useLayoutEffect()\n if (callee.type === AST_NODE_TYPES.MemberExpression && !callee.computed) {\n return (\n callee.property.type === AST_NODE_TYPES.Identifier &&\n (callee.property.name === \"useEffect\" ||\n callee.property.name === \"useLayoutEffect\")\n );\n }\n\n return false;\n};\n\n/**\n * Helper function: Check if an expression statement contains a conditional addEventListener.\n */\nconst isConditionalAddEventListener = (node: TSESTree.Statement): boolean => {\n if (node.type !== AST_NODE_TYPES.IfStatement) {\n return false;\n }\n\n // Check if statement has addEventListener in consequent block\n if (node.consequent.type === AST_NODE_TYPES.BlockStatement) {\n for (const statement of node.consequent.body) {\n if (\n statement.type === AST_NODE_TYPES.ExpressionStatement &&\n isAddEventListenerCall(statement.expression)\n ) {\n return true;\n }\n }\n }\n\n // Check inline if without block\n if (\n node.consequent.type === AST_NODE_TYPES.ExpressionStatement &&\n isAddEventListenerCall(node.consequent.expression)\n ) {\n return true;\n }\n\n // Check else/alternate block\n if (node.alternate?.type === AST_NODE_TYPES.BlockStatement) {\n for (const statement of node.alternate.body) {\n if (\n statement.type === AST_NODE_TYPES.ExpressionStatement &&\n isAddEventListenerCall(statement.expression)\n ) {\n return true;\n }\n }\n }\n\n // Check inline else\n return (\n node.alternate?.type === AST_NODE_TYPES.ExpressionStatement &&\n isAddEventListenerCall(node.alternate.expression)\n );\n};\n\n/**\n * Helper function: Check if expression has conditional addEventListener (logical or ternary).\n */\nconst isConditionalExpression = (expression: TSESTree.Expression): boolean => {\n // Check logical AND: x && element.addEventListener(...)\n if (\n expression.type === AST_NODE_TYPES.LogicalExpression &&\n expression.operator === \"&&\" &&\n isAddEventListenerCall(expression.right)\n ) {\n return true;\n }\n\n // Check ternary: x ? element.addEventListener(...) : ...\n if (expression.type === AST_NODE_TYPES.ConditionalExpression) {\n if (isAddEventListenerCall(expression.consequent)) {\n return true;\n }\n if (isAddEventListenerCall(expression.alternate)) {\n return true;\n }\n }\n\n return false;\n};\n\n/**\n * Helper function: Search block for addEventListener, return true if found.\n */\nconst findAddEventListenerInBlock = (\n statements: TSESTree.Statement[],\n): boolean => {\n for (const statement of statements) {\n if (statement.type === AST_NODE_TYPES.ExpressionStatement) {\n if (isAddEventListenerCall(statement.expression)) {\n return true;\n }\n if (isConditionalExpression(statement.expression)) {\n return true;\n }\n }\n }\n\n return false;\n};\n\n/**\n * Helper function: Search block for removeEventListener, return true if found.\n */\nconst findRemoveEventListenerInBlock = (\n statements: TSESTree.Statement[],\n): boolean => {\n for (const statement of statements) {\n if (\n statement.type === AST_NODE_TYPES.ExpressionStatement &&\n isRemoveEventListenerCall(statement.expression)\n ) {\n return true;\n }\n }\n\n return false;\n};\n\n/**\n * Helper function: Search cleanup function (return statement) for removeEventListener.\n */\nconst findRemoveEventListenerInCleanup = (\n statements: TSESTree.Statement[],\n): boolean => {\n for (const statement of statements) {\n if (statement.type !== AST_NODE_TYPES.ReturnStatement) {\n continue;\n }\n\n const cleanupFunction = statement.argument;\n\n if (\n cleanupFunction &&\n (cleanupFunction.type === AST_NODE_TYPES.ArrowFunctionExpression ||\n cleanupFunction.type === AST_NODE_TYPES.FunctionExpression) &&\n cleanupFunction.body?.type === AST_NODE_TYPES.BlockStatement &&\n cleanupFunction.body.body &&\n findRemoveEventListenerInBlock(cleanupFunction.body.body)\n ) {\n return true;\n }\n\n // Handle arrow expression body: () => element.removeEventListener(...)\n if (\n cleanupFunction?.type === AST_NODE_TYPES.ArrowFunctionExpression &&\n cleanupFunction.body &&\n isRemoveEventListenerCall(cleanupFunction.body)\n ) {\n return true;\n }\n }\n\n return false;\n};\n\n/**\n * Helper function: Check if there's a return statement with a cleanup function.\n */\nconst hasReturnStatement = (statements: TSESTree.Statement[]): boolean => {\n for (const statement of statements) {\n if (\n statement.type === AST_NODE_TYPES.ReturnStatement &&\n statement.argument &&\n (statement.argument.type === AST_NODE_TYPES.ArrowFunctionExpression ||\n statement.argument.type === AST_NODE_TYPES.FunctionExpression)\n ) {\n return true;\n }\n }\n\n return false;\n};\n\n/**\n * Helper function: Check if useEffect body has conditional addEventListener.\n */\nconst hasConditionalAddEventListener = (\n statements: TSESTree.Statement[],\n): boolean => {\n for (const statement of statements) {\n if (isConditionalAddEventListener(statement)) {\n return true;\n }\n\n if (\n statement.type === AST_NODE_TYPES.ExpressionStatement &&\n isConditionalExpression(statement.expression)\n ) {\n return true;\n }\n }\n\n return false;\n};\n\n/**\n * Helper function: Unwrap ChainExpression to get the actual CallExpression.\n */\nconst unwrapToCallExpression = (\n node: unknown,\n): TSESTree.CallExpression | null => {\n let currentNode = node;\n\n // Handle ChainExpression wrapping the entire CallExpression\n if (\n currentNode instanceof Object &&\n \"type\" in currentNode &&\n currentNode.type === AST_NODE_TYPES.ChainExpression\n ) {\n currentNode = (currentNode as TSESTree.ChainExpression).expression;\n }\n\n // Validate it's a CallExpression\n if (\n !(currentNode instanceof Object) ||\n !(\"type\" in currentNode) ||\n currentNode.type !== AST_NODE_TYPES.CallExpression\n ) {\n return null;\n }\n\n return currentNode as TSESTree.CallExpression;\n};\n\n/**\n * Helper function: Check if addEventListener call has \\{ once: true \\} option.\n */\nconst hasOnceOption = (call: unknown): boolean => {\n if (!isEventListenerMethodCall(call, \"addEventListener\")) {\n return false;\n }\n\n const callExpression = unwrapToCallExpression(call);\n\n if (!callExpression) {\n return false;\n }\n\n const optionsArgument = callExpression.arguments[2];\n\n if (optionsArgument?.type === AST_NODE_TYPES.ObjectExpression) {\n for (const property of optionsArgument.properties) {\n if (\n property.type === AST_NODE_TYPES.Property &&\n property.key.type === AST_NODE_TYPES.Identifier &&\n property.key.name === \"once\" &&\n property.value.type === AST_NODE_TYPES.Literal &&\n property.value.value === true\n ) {\n return true;\n }\n }\n }\n\n return false;\n};\n\n/**\n * Helper function: Check if there's any addEventListener in block that is not once.\n * Returns true if at least one addEventListener does NOT have \\{ once: true \\}.\n */\nconst hasNonOnceAddEventListenerInBlock = (\n statements: TSESTree.Statement[],\n): boolean => {\n for (const statement of statements) {\n if (\n statement.type === AST_NODE_TYPES.ExpressionStatement &&\n isEventListenerMethodCall(statement.expression, \"addEventListener\") &&\n !hasOnceOption(statement.expression)\n ) {\n return true;\n }\n }\n\n return false;\n};\n\nexport default createEslintRule<Options, MessageIds>({\n name: RULE_NAME,\n meta: {\n type: \"problem\",\n docs: {\n description:\n \"enforces best practices around addEventListener method in React components.\",\n url: \"https://github.com/AndreaPontrandolfo/eslint-plugin-fsecond/blob/master/docs/rules/valid-event-listener.md\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n additionalProperties: false,\n properties: {\n requireUseEventListenerHook: {\n description: \"Require the use of a useEventListener hook\",\n type: \"boolean\",\n },\n },\n },\n ],\n defaultOptions: [\n {\n requireUseEventListenerHook: true,\n },\n ],\n messages: {\n \"required-cleanup\":\n \"Missing a cleanup function for the addEventListener in React useEffect.\",\n \"required-remove-eventListener\":\n \"Missing a matching removeEventListener in the React useEffect cleanup.\",\n \"no-conditional-addeventlistener\":\n \"Don't wrap addEventListener in a condition in React components.\",\n \"require-use-event-listener-hook\":\n \"Use a useEventListener hook from a React hooks library instead of manually adding and removing event listeners.\",\n },\n },\n defaultOptions: [\n {\n requireUseEventListenerHook: true,\n },\n ],\n create(context) {\n const providedFirstOption = context.options[0] ?? {};\n const { requireUseEventListenerHook } = {\n requireUseEventListenerHook: true,\n ...providedFirstOption,\n };\n\n return {\n ExpressionStatement(node) {\n const expression = node?.expression;\n\n if (expression.type !== AST_NODE_TYPES.CallExpression) {\n return;\n }\n\n if (!isUseEffectOrUseLayoutEffectCall(expression)) {\n return;\n }\n\n const firstArgument = expression.arguments[0];\n const isFunctionExpression =\n firstArgument?.type === AST_NODE_TYPES.ArrowFunctionExpression ||\n firstArgument?.type === AST_NODE_TYPES.FunctionExpression;\n\n const useEffectBodyInternalItems =\n expression?.arguments &&\n expression.arguments.length > 0 &&\n isFunctionExpression &&\n firstArgument.body?.type === AST_NODE_TYPES.BlockStatement &&\n firstArgument.body.body;\n\n if (\n !useEffectBodyInternalItems ||\n useEffectBodyInternalItems.length === 0\n ) {\n return;\n }\n\n // Check for conditional addEventListener first\n const hasConditionalAdd = hasConditionalAddEventListener(\n useEffectBodyInternalItems,\n );\n\n if (hasConditionalAdd) {\n context.report({\n node,\n messageId: \"no-conditional-addeventlistener\",\n });\n\n return;\n }\n\n // Check for addEventListener in the main body\n const hasAddEventListener = findAddEventListenerInBlock(\n useEffectBodyInternalItems,\n );\n\n if (!hasAddEventListener) {\n return;\n }\n\n // Check if hook is required\n if (requireUseEventListenerHook) {\n context.report({\n node,\n messageId: \"require-use-event-listener-hook\",\n });\n\n return;\n }\n\n // Check for cleanup\n const hasReturnStmt = hasReturnStatement(useEffectBodyInternalItems);\n const hasNonOnceAddEventListener = hasNonOnceAddEventListenerInBlock(\n useEffectBodyInternalItems,\n );\n\n // If all addEventListener calls use { once: true }, no cleanup is needed\n // This is safe for AbortSignal patterns where the event fires at most once\n if (!hasNonOnceAddEventListener) {\n return;\n }\n\n const hasRemoveEventListener = findRemoveEventListenerInCleanup(\n useEffectBodyInternalItems,\n );\n\n if (!hasRemoveEventListener) {\n if (hasReturnStmt) {\n context.report({\n node,\n messageId: \"required-remove-eventListener\",\n });\n } else {\n context.report({\n node,\n messageId: \"required-cleanup\",\n });\n }\n }\n },\n };\n },\n});\n","import packageJson from \"../package.json\";\nimport noInlineInterfaces from \"./rules/no-inline-interfaces\";\nimport noRedundantJsxPropUsage from \"./rules/no-redundant-jsx-prop-usage\";\n// import preferAliasedPath from \"./rules/prefer-aliased-path\";\nimport preferDestructuredOptionals from \"./rules/prefer-destructured-optionals\";\nimport validEventListener from \"./rules/valid-event-listener\";\n\nconst eslintPluginShortName = \"fsecond\";\n\nconst plugin = {\n meta: { name: packageJson.name, version: packageJson.version },\n configs: {},\n rules: {\n \"prefer-destructured-optionals\": preferDestructuredOptionals,\n \"valid-event-listener\": validEventListener,\n // \"prefer-aliased-path\": preferAliasedPath,\n \"no-inline-interfaces\": noInlineInterfaces,\n \"no-redundant-jsx-prop-usage\": noRedundantJsxPropUsage,\n },\n};\n\nconst configs = {\n recommended: [\n {\n plugins: {\n fsecond: plugin,\n },\n rules: {\n [`${eslintPluginShortName}/prefer-destructured-optionals`]: 2,\n [`${eslintPluginShortName}/valid-event-listener`]: 2,\n [`${eslintPluginShortName}/no-inline-interfaces`]: [\n 2,\n { checkGenericTypes: false, checkReturnTypes: true },\n ],\n },\n },\n ],\n recommendedTypeChecked: [\n {\n plugins: {\n fsecond: plugin,\n },\n rules: {\n [`${eslintPluginShortName}/prefer-destructured-optionals`]: 2,\n [`${eslintPluginShortName}/valid-event-listener`]: 2,\n [`${eslintPluginShortName}/no-inline-interfaces`]: [\n 2,\n { checkGenericTypes: false, checkReturnTypes: true },\n ],\n [`${eslintPluginShortName}/no-redundant-jsx-prop-usage`]: 2,\n },\n },\n ],\n};\n\nplugin.configs = configs;\n\ntype Plugin = typeof plugin & {\n configs: typeof configs;\n};\n\nexport default plugin as Plugin;\n"],"mappings":"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