Skip to content

Start using @typescript-eslint/no-unnecessary-condition eslint rule - #1200

Merged
grigasp merged 4 commits into
nextfrom
eslint/no-unnecessary-condition-rule
Jan 28, 2026
Merged

Start using @typescript-eslint/no-unnecessary-condition eslint rule#1200
grigasp merged 4 commits into
nextfrom
eslint/no-unnecessary-condition-rule

Conversation

@grigasp

@grigasp grigasp commented Jan 27, 2026

Copy link
Copy Markdown
Member

Added @typescript-eslint/no-unnecessary-condition eslint rule, which found a number of unnecessary checks, especially optional chaining and nullish coalescing.

3 cases when I had to unnecessarily make changes to our code to overcome it:

  1. Checking if object is of type, based on derived type attribute (usually in type guards):

    // this used to work, but now eslint complains:
    function isFoo(obj: BaseType): obj is DerivedType {
      return (obj as DerivedType).derivedAttribute !== undefined;
    }
    // do this instead:
    function isFoo(obj: BaseType): obj is DerivedType {
      return "derivedAttribute" in obj;
    }
  2. Value changes in functions:

     useEffect(() => {
       let disposed = false;
       asyncFunction().then((result) => {
         // eslint complains that `disposed` is always `false` here, define it as `let disposed = false as boolean` instead,
         // this is one of the known issues with this rule
         if (!disposed) {
           setState(result);
         }
       });
       return () => {
          disposed = true;
       };
     }, [dependencies]);
  3. Object property access:

    const value: {[key: string]: number} = {};
    // eslint complains that value is always truthy here, use `Map` instead
    if (!value) {
      // ...
    }

@grigasp
grigasp requested a review from a team as a code owner January 27, 2026 13:33
@changeset-bot

changeset-bot Bot commented Jan 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e93fd15

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 7 packages
Name Type
@itwin/presentation-shared Major
@itwin/presentation-hierarchies-react Patch
@itwin/presentation-core-interop Patch
@itwin/presentation-components Patch
@itwin/presentation-testing Patch
@itwin/presentation-hierarchies Patch
@itwin/unified-selection Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@saskliutas

Copy link
Copy Markdown
Member
// this used to work, but now eslint complains:
function isFoo(obj: BaseType): obj is DerivedType {
  return (obj as DerivedType).derivedAttribute !== undefined;
}
// do this instead:
function isFoo(obj: BaseType): obj is DerivedType {
  return "derivedAttribute" in obj;
}

Looks like these are not equivalent, Shouldn't second be "derivedAttribute" in obj && obj.derivedAttribute !== undefined to fully match?

@grigasp

grigasp commented Jan 27, 2026

Copy link
Copy Markdown
Member Author
// this used to work, but now eslint complains:
function isFoo(obj: BaseType): obj is DerivedType {
  return (obj as DerivedType).derivedAttribute !== undefined;
}
// do this instead:
function isFoo(obj: BaseType): obj is DerivedType {
  return "derivedAttribute" in obj;
}

Looks like these are not equivalent, Shouldn't second be "derivedAttribute" in obj && obj.derivedAttribute !== undefined to fully match?

I agree they're not fully equivalent, however, for these type guard type of use cases, I think "derivedAttribute" in obj check is sufficient:

  • if derivedAttribute is non-optional, then checking for its existence is enough,
  • if it's optional, then I'd argue the attribute is not suitable for checking the type at all.

Do you see any specific case where not having && obj.derivedAttribute !== undefined would produce invalid result?

@saskliutas

saskliutas commented Jan 27, 2026

Copy link
Copy Markdown
Member
// this used to work, but now eslint complains:
function isFoo(obj: BaseType): obj is DerivedType {
  return (obj as DerivedType).derivedAttribute !== undefined;
}
// do this instead:
function isFoo(obj: BaseType): obj is DerivedType {
  return "derivedAttribute" in obj;
}

Looks like these are not equivalent, Shouldn't second be "derivedAttribute" in obj && obj.derivedAttribute !== undefined to fully match?

I agree they're not fully equivalent, however, for these type guard type of use cases, I think "derivedAttribute" in obj check is sufficient:

  • if derivedAttribute is non-optional, then checking for its existence is enough,
  • if it's optional, then I'd argue the attribute is not suitable for checking the type at all.

Do you see any specific case where not having && obj.derivedAttribute !== undefined would produce invalid result?

No, just wanted to make sure that we won't miss this. And I agree that we should not type check based on optional properties.

@grigasp
grigasp requested a review from Copilot January 27, 2026 14:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR enables the @typescript-eslint/no-unnecessary-condition ESLint rule to eliminate unnecessary null/undefined checks and optional chaining operations. The changes primarily remove redundant type guards, replace object property checks with in operator, convert object-based maps to Map instances, and add explicit type annotations where the rule cannot infer mutability.

Changes:

  • Removed unnecessary optional chaining (?.) and nullish coalescing (??) operators where types guarantee non-null values
  • Replaced object property type guards (obj.prop !== undefined) with the in operator for better type narrowing
  • Converted plain object maps to Map instances to avoid always-truthy object references
  • Added explicit boolean type annotations for variables modified in closures to work around rule limitations

Reviewed changes

Copilot reviewed 60 out of 60 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
eslint.base.config.js Added the @typescript-eslint/no-unnecessary-condition rule configuration
packages/shared/src/shared/Metadata.ts Made multiplicity required in RelationshipConstraint interface
packages/shared/src/shared/Values.ts Changed Point2d/Point3d type guards to use in operator
packages/shared/src/test/MetadataProviderStub.ts Converted schema stubs storage from object to Map
packages/testing/src/test/HierarchyBuilder.test.ts Removed redundant optional chaining on opts parameter
packages/hierarchies-react/src/presentation-hierarchies-react/internal/UseUnifiedSelection.ts Converted imodel selectables storage from object to Map
packages/hierarchies-react/src/presentation-hierarchies-react/stratakit/TreeRenderer.tsx Removed unnecessary optional chaining and unused helper function
packages/hierarchies-react/src/presentation-hierarchies-react/UseTree.ts Added explicit boolean type annotation for closure variable
packages/components/src/presentation-components/propertygrid/DataProvider.ts Removed unnecessary null checks and added version compatibility comments
apps/test-app/frontend/src/index.tsx Simplified promise array initialization by removing undefined check

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/shared/src/test/ecsql-snippets/ECSqlJoinSnippets.test.ts

Copilot AI commented Jan 27, 2026

Copy link
Copy Markdown
Contributor

@grigasp I've opened a new pull request, #1201, to work on those changes. Once the pull request is ready, I'll request review from you.

@grigasp
grigasp enabled auto-merge (squash) January 27, 2026 15:03
Comment thread packages/components/src/presentation-components/common/ContentBuilder.ts Outdated
@grigasp
grigasp merged commit b9a2ff0 into next Jan 28, 2026
6 checks passed
@grigasp
grigasp deleted the eslint/no-unnecessary-condition-rule branch January 28, 2026 07:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants