11import { AST_NODE_TYPES , ESLintUtils , type TSESTree } from "@typescript-eslint/utils"
2- import type ts from "typescript"
2+ import ts from "typescript"
33
44import { createRule } from "../utils/create-rule.js"
55
@@ -594,17 +594,21 @@ function functionReturnsString(
594594) : boolean {
595595 try {
596596 const tsNode = parserServices . esTreeNodeToTSNodeMap . get ( node )
597- const signature = typeChecker . getSignatureFromDeclaration ( tsNode as ts . SignatureDeclaration )
598597
599- if ( signature === undefined ) {
600- // No signature info - allow by name to avoid false positives
598+ // Get the type of the function itself
599+ const functionType = typeChecker . getTypeAtLocation ( tsNode )
600+ const callSignatures = functionType . getCallSignatures ( )
601+
602+ if ( callSignatures . length === 0 ) {
603+ // No call signatures - allow by name to avoid false positives
601604 return true
602605 }
603606
604- const returnType = typeChecker . getReturnTypeOfSignature ( signature )
605-
606- // Check if it's a string type or union containing string
607- return isStringishType ( returnType , typeChecker )
607+ // Check all call signatures - all must return string-ish
608+ return callSignatures . every ( ( signature ) => {
609+ const returnType = typeChecker . getReturnTypeOfSignature ( signature )
610+ return isStringishType ( returnType , typeChecker )
611+ } )
608612 } catch {
609613 // Type checking can fail - allow by name to avoid false positives
610614 return true
@@ -614,33 +618,33 @@ function functionReturnsString(
614618/**
615619 * Checks if a type is string-ish: string, string literal, or union of these with null/undefined.
616620 */
617- function isStringishType ( type : ts . Type , typeChecker : ts . TypeChecker ) : boolean {
621+ function isStringishType ( type : ts . Type , _typeChecker : ts . TypeChecker ) : boolean {
622+ const flags = type . getFlags ( )
623+
618624 // Check if it's a union type
619625 if ( type . isUnion ( ) ) {
620626 // All non-null/undefined members must be string-ish
621627 const nonNullableTypes = type . types . filter ( ( t ) => {
622- const flags = t . getFlags ( )
628+ const f = t . getFlags ( )
623629 // Skip null and undefined
624- return ! ( flags & 32768 ) && ! ( flags & 65536 ) // Null = 32768, Undefined = 65536
630+ return ( f & ts . TypeFlags . Null ) === 0 && ( f & ts . TypeFlags . Undefined ) === 0
625631 } )
626632
627633 // If only null/undefined, that's not a string return
628634 if ( nonNullableTypes . length === 0 ) {
629635 return false
630636 }
631637
632- return nonNullableTypes . every ( ( t ) => isStringishType ( t , typeChecker ) )
638+ return nonNullableTypes . every ( ( t ) => isStringishType ( t , _typeChecker ) )
633639 }
634640
635- const flags = type . getFlags ( )
636-
637- // String type (4) or string literal (128)
638- if ( ( flags & 4 ) !== 0 || ( flags & 128 ) !== 0 ) {
641+ // String type or string literal
642+ if ( ( flags & ts . TypeFlags . String ) !== 0 || ( flags & ts . TypeFlags . StringLiteral ) !== 0 ) {
639643 return true
640644 }
641645
642646 // Template literal type
643- if ( ( flags & 134217728 ) !== 0 ) {
647+ if ( ( flags & ts . TypeFlags . TemplateLiteral ) !== 0 ) {
644648 return true
645649 }
646650
0 commit comments