Skip to content

linter: no-unnecessary-condition false positive with overloaded method accepting T | undefined #20151

@DreierF

Description

@DreierF

What version of Oxlint are you using?

1.51.0, oxlint-tsgolint 0.16.0

What command did you run?

oxlint

What does your .oxlintrc.json (or oxlint.config.ts) config file look like?

 {                                                                                                                                                                                                                                                                                                                
    "plugins": ["typescript"],                                                         
    "options": {                                                                                                                                                                                                                                                                                                   
      "typeAware": true                                                                
    },                                                                                                                                                                                                                                                                                                             
    "rules": {
      "@typescript-eslint/no-unnecessary-condition": "error"                                                                                                                                                                                                                                                       
    }                                                                                                                                                                                                                                                                                                              
  } 

What happened?

@typescript-eslint/no-unnecessary-condition reports a false positive on ?. after a call to an overloaded method when the argument type is T | undefined.

The method has two overloads:

  1. valueOf(entry: string): MyEnum (non-nullable return)
  2. valueOf(entry: string | undefined): MyEnum | undefined (nullable return)

When called with an string | undefined argument, TypeScript correctly selects the second overload, returning MyEnum | undefined. The ?. is therefore necessary. oxlint appears to resolve to the first overload instead and considers the value non-nullish.

tsconfig.json:

  {                                                                                    
    "compilerOptions": {                       
      "module": "ESNext",                                                                                                                                                                                                                                                                                          
      "target": "ES2023",                                                                                                                                                                                                                                                                                          
      "moduleResolution": "bundler",                                                                                                                                                                                                                                                                               
      "strict": true,                                                                                                                                                                                                                                                                                              
      "noEmit": true,                                                                                                                                                                                                                                                                                              
      "skipLibCheck": true                                                                                                                                                                                                                                                                                         
    },                                                                                                                                                                                                                                                                                                             
    "include": ["src"]                                                                                                                                                                                                                                                                                             
  }          

src/example.ts:

  class MyEnum {                                                                                                                                                                                                                                                                                                   
    public readonly readableName: string;                                              
                                                                                                                                                                                                                                                                                                                   
    private constructor(readableName: string) {
      this.readableName = readableName;                                                                                                                                                                                                                                                                            
    }                                                                                  
                                               
    public static readonly A = new MyEnum('Type A');                                                                                                                                                                                                                                                               
    public static readonly B = new MyEnum('Type B');
                                                                                                                                                                                                                                                                                                                   
    public static valueOf(entry: string): MyEnum;                                      
    public static valueOf(entry: string | undefined): MyEnum | undefined;                                                                                                                                                                                                                                          
    public static valueOf(entry: string | undefined): MyEnum | undefined {             
      if (entry === 'A') {                                                                                                                                                                                                                                                                                         
        return MyEnum.A;
      }                                                                                                                                                                                                                                                                                                            
      if (entry === 'B') {                                                             
        return MyEnum.B;                       
      }
      return undefined;                                                                                                                                                                                                                                                                                            
    }
  }                                                                                                                                                                                                                                                                                                                
                                                                                       
  type Event = {                               
    type?: string;
  };                                                                                                                                                                                                                                                                                                               
   
  export function getReadableName(event: Event): string {                                                                                                                                                                                                                                                                 
    return MyEnum.valueOf(event.type)?.readableName ?? 'unknown';                      
  }                                                                                                                                                                                                                                                                                                                
                                                                                       
  export { MyEnum, getReadableName };        

Reported diagnostic:

    x typescript-eslint(no-unnecessary-condition): Unnecessary optional chain on a non-nullish value.                                                                                                                                                                                                              
      ,-[src/example.ts:30:9]                                                                                                                                                                                                                                                                                      
   29 | function getReadableName(event: Event): string {                                                                                                                                                                                                                                                           
   30 |     return MyEnum.valueOf(event.type)?.readableName ?? 'unknown';                                                                                                                                                                                                                                          
      :            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                                                                                                                                                                                                                                                        
   31 | }                                                                                                                                                                                                                                                                                                          
      `----                                                                            

Metadata

Metadata

Assignees

Labels

Type

Priority

None yet

Effort

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions