Thanks to visit codestin.com
Credit goes to github.com

Skip to content

New rule: avoid assertions if you can just use ! #1922

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

Closed
fregante opened this issue Apr 20, 2020 · 6 comments
Closed

New rule: avoid assertions if you can just use ! #1922

fregante opened this issue Apr 20, 2020 · 6 comments
Labels
enhancement: new plugin rule New rule request for eslint-plugin package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin

Comments

@fregante
Copy link
Contributor

fregante commented Apr 20, 2020

TS2532: Object is possibly 'undefined'.

I saw many newcomers (including myself) occasionally solving this error with assertions. For example:

function (text: string | undefined): string {
	return text as string; // ❌
}

Can be also written as:

function (text: string | undefined): string {
	return text!; // ✅
}

The assertion is unnecessary, longer and less safe

@fregante fregante added package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin triage Waiting for team members to take a look labels Apr 20, 2020
@bradzacher
Copy link
Member

bradzacher commented Apr 20, 2020

My question would be "why are you using a non-null assertion at all?"

@fregante
Copy link
Contributor Author

fregante commented Apr 20, 2020

Because the DOM.

Event#target is pretty much guaranteed to be present, but its type is EventTarget | undefined.

Or in this specific case:

document.querySelector('[data-channel]')!.dataset.channel
// string | undefined                   ^ forgive this, demo only

I just selected by data-channel, .dataset.channel cannot be undefined

@bradzacher bradzacher added enhancement: new plugin rule New rule request for eslint-plugin and removed triage Waiting for team members to take a look labels Apr 20, 2020
@adidahiya
Copy link

How would you write a general rule which allows ! like that? The example function in your OP is not safe in all cases. And re: #1922 (comment)... TypeScript doesn't know what your DOM looks like.

@bradzacher
Copy link
Member

bradzacher commented Apr 20, 2020

TypeScript doesn't know what your DOM looks like.

This is the point - the types must be defined based on supporting every single query-case, and have no context on how you're actually querying or consuming them.

This is a common problem in every typed language when it comes to boundaries between a query language that generates no types. There is just some weirdness that needs to be worked around with non-null assertions or similar. That, or you add always-true checks into the code to correct the code.

I hate it, but the alternative is ad-hoc, automatic generation of types based on a separate build chain, which is a lot of work.

As another example, we have similar problems in this project with ESLint selectors and types - eg:

const selectors = {
  'TSNonNullExpression > TSNonNullExpression'(node: TSESTree. TSNonNullExpression) {
    // guaranteed by the selector, but currently impossible to represent in types
    const parent = node.parent as TSESTree.TSNonNullExpression;
  },
  'VariableDeclarator[init]'(node: TSESTree.VariableDeclarator) {
    // selector guarantees non-null, but is hard to nicely represent in types
    const init = node.init!;
  },
  // the only way to properly type this at declaration time
  'VariableDeclarator[init]'(node: TSESTree.VariableDeclarator & {
    init: NonNullable<TSESTree.VariableDeclarator['init']>;
  }) {
    const init = node.init; // correctly non-nullish
  },
  // or solve it with runtime checks
  'VariableDeclarator[init]'(node: TSESTree.VariableDeclarator) {
    /* istanbul ignore if */ if (node.init == null) {
      return;
    }
    const init = node.init;
  },
  'VariableDeclarator[init]'(node: TSESTree.VariableDeclarator) {
    const init = nullthrows(node.init, 'found no init on VariableDeclarator');
  },
}

function nullthrows<T>(thing: T, msg: string): NonNullable<T> {
  if (thing == null) { throw new Error(msg) }
  return thing;
}

How would you write a general rule which allows ! like that

quick-and-dirty-very-rough-and-untested-pseudocode:

return {
  'TSAsExpression, TSTypeAssertion'(node) {
    const baseType = getTypeAtLocation(node.expression);
    if (!isUnionType(baseType) || !containsNullishTypes(baseType)) {
      // non-nullish and non-union types cannot be non-null asserted on
      return;
    }
    const assertedType = getTypeAtLocation(node.expression);
    if (containsNullishTypes(baseType)) {
      // asserting from string | null | undefined to string | null is fine
      return;
    }
    
    for (const base of baseType.filter(nullishTypes)) {
      if (!assertedType.contains(base)) {
        return; // asserted type is not an exact NonNullable conversion
      }
    }
    
    context.report('use a ! instead');
  },
}

@glen-84
Copy link
Contributor

glen-84 commented Dec 16, 2020

Duplicate of #202.

@bradzacher
Copy link
Member

Yup!

The new rule non-nullable-assertion-style covers this.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jan 16, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement: new plugin rule New rule request for eslint-plugin package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin
Projects
None yet
Development

No branches or pull requests

4 participants