Skip to content

fix: [no-unnecessary-condition] use assignability APIs in no-unnecessary-condition (POC) #10378

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

kirkwaiblinger
Copy link
Member

@kirkwaiblinger kirkwaiblinger commented Nov 23, 2024

PR Checklist

Overview

This fundamentally changes the truthiness/falsiness algorithm used in no-unnecessary-condition. Up until now, the rule has checked various heuristics for possible falsiness: essentially whether the type is a known falsy literal and whether the type has TS's (inconsistent) PossiblyFalsy flag set. The proposed change is to use the assignability API to simply check whether one of JS's few falsy types is assignable to the type being investigated.

This has a few interesting consequences that improve correctness....

// used to flag, no longer flags
function usesEmptyObject(x: {}) {
  if (x) {
    console.log('truthy case')
  } else {
    console.log('truthy case')
  }
}
// because 
usesEmptyObject(false);

// used to flag, no longer flags
function usesNumberyObject(x: { toFixed: () => string }) {
  if (x) {
    console.log('truthy case')
  } else {
    console.log('truthy case')
  }
}
// because 
usesNumberyObject(0);

Annoyingly, this also has the drawback that the following no longer flags.

const neverNull = {};
if (neverNull) {
}

That's due to TS's (questionable?) decision to give neverNull type {} instead of type object, even when initializing a const variable.

The following do still flag

const neverNull: object = {};
if (neverNull) {
}

// this is a TS error too
if ({}) {
}

declare function assert(x: unknown): asserts x;
// flagged by no-unnecessary-condition and not a TS error.
assert({});

If that specific case is something we want to preserve, we could do so with scope analysis.

@typescript-eslint
Copy link
Contributor

Thanks for the PR, @kirkwaiblinger!

typescript-eslint is a 100% community driven project, and we are incredibly grateful that you are contributing to that community.

The core maintainers work on this in their personal time, so please understand that it may not be possible for them to review your work immediately.

Thanks again!


🙏 Please, if you or your company is finding typescript-eslint valuable, help us sustain the project by sponsoring it transparently on https://opencollective.com/typescript-eslint.

Copy link

netlify bot commented Nov 23, 2024

Deploy Preview for typescript-eslint ready!

Name Link
🔨 Latest commit 0b8d6b8
🔍 Latest deploy log https://app.netlify.com/sites/typescript-eslint/deploys/67423489b440f60008947bea
😎 Deploy Preview https://deploy-preview-10378--typescript-eslint.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 99 (🟢 up 1 from production)
Accessibility: 100 (no change from production)
Best Practices: 92 (no change from production)
SEO: 98 (no change from production)
PWA: 80 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link

nx-cloud bot commented Nov 23, 2024

☁️ Nx Cloud Report

CI is running/has finished running commands for commit 0b8d6b8. As they complete they will appear below. Click to see the status, the terminal output, and the build insights.

📂 See all runs for this CI Pipeline Execution


✅ Successfully ran 2 targets

Sent with 💌 from NxCloud.

Copy link

codecov bot commented Nov 23, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 86.67%. Comparing base (88e4c66) to head (0b8d6b8).
Report is 10 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #10378      +/-   ##
==========================================
- Coverage   86.69%   86.67%   -0.03%     
==========================================
  Files         434      434              
  Lines       15227    15226       -1     
  Branches     4445     4442       -3     
==========================================
- Hits        13201    13197       -4     
- Misses       1673     1675       +2     
- Partials      353      354       +1     
Flag Coverage Δ
unittest 86.67% <100.00%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...slint-plugin/src/rules/no-unnecessary-condition.ts 98.00% <100.00%> (-1.21%) ⬇️

@kirkwaiblinger kirkwaiblinger changed the title POC - using assignability APIs in no-unnecessary-condition fix: [no-unnecessary-condition] use assignability APIs in no-unnecessary-condition (POC) Nov 23, 2024
Copy link
Member

@JoshuaKGoldberg JoshuaKGoldberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this direction! Nicely done 🙂

@@ -107,7 +107,7 @@ function assert(condition: unknown): asserts condition {

assert(false); // Unnecessary; condition is always falsy.

const neverNull = {};
const neverNull = { someProperty: 'someValue' };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's due to TS's (questionable?) decision to give neverNull type {} instead of type object, even when initializing a const variable.

😩 this again... is there a TypeScript issue filed that we can reference? It feels like a bug to me.

I also don't mind it very much. It's not often that folks use {}. This docs change kind of makes it more realistic to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😩 this again... is there a TypeScript issue filed that we can reference? It feels like a bug to me.

That's a good question. I'll take an action item to investigate this.

I also don't mind it very much. It's not often that folks use {}. This docs change kind of makes it more realistic to me.

Agreed that the docs change is probably an improvement. But, the false negative here makes the rule a little less understandable to me, so I'd lean on the side of wanting it to be correct if we can get it to be.

Copy link
Member Author

@kirkwaiblinger kirkwaiblinger Nov 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, dear, the moment I start playing around with this, I immediately find infuriating behavior around {}....

function takesObject(x: object) {
  if (x) {
    console.log('should always be true')
  } else {
    console.error('this should be unreachable');
  }
}
const o: {} = 0;
// no error here!?!?!?!?
takesObject(o);

I hate the {} type so much.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding const o = {}; // has type {}, started discord conversation at https://discord.com/channels/508357248330760243/508357638677856287/1310419206260654081.

Regarding {} -> object assignments, filed microsoft/TypeScript#60582... and immediately learned this is a duplicate of microsoft/TypeScript#56205, in which this behavior is deemed intentional 🫤.

Reminder to self: I'm pretty sure we'll have some good info to add to the motivation for https://typescript-eslint.io/rules/no-empty-object-type/ as an outcome of these discussions 😆

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DO NOT MERGE PRs which should not be merged yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants