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

Skip to content

Rule proposal: no-unsafe-type-assertion #7173

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
6 tasks done
lgenzelis opened this issue Jul 8, 2023 · 13 comments · Fixed by #10051
Closed
6 tasks done

Rule proposal: no-unsafe-type-assertion #7173

lgenzelis opened this issue Jul 8, 2023 · 13 comments · Fixed by #10051
Labels
accepting prs Go ahead, send a pull request that resolves this issue enhancement: new plugin rule New rule request for eslint-plugin locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin

Comments

@lgenzelis
Copy link
Contributor

lgenzelis commented Jul 8, 2023

Before You File a Proposal Please Confirm You Have Done The Following...

My proposal is suitable for this project

  • My proposal specifically checks TypeScript syntax, or it proposes a check that requires type information to be accurate.
  • My proposal is not a "formatting rule"; meaning it does not just enforce how code is formatted (whitespace, brace placement, etc).
  • I believe my proposal would be useful to the broader TypeScript community (meaning it is not a niche proposal).

Description

One of the hardest thing to explain to people not experienced in TS is when it's ok to use as. I default to tell people "don't use as", and I constantly have to deal with that in code reviews. I'd love to have a rule that only allows type assertions that widen a type. For example, as unknown is always totally ok. as string | number is safe on a string, but unsafe in a string | boolean.

This rule should only allow doing x as Type in the cases where the type of x is assignable to type. So, the rule of thumb would be: if const a: Type = x is fine, then x as Type is fine. Otherwise it's not.

Fail Cases

const x = 'something';
const y = x as unknown as number; // Type `unknown` is not assignable to type `number` 🚫

//-----------

function f() {
  return Math.random() < 0.5 ? 42 : 'meh';
}
const z = f() as number; // Type `number | string` is not assignable to type `number` 🚫

Pass Cases

const a = 'something';
const b = a as unknown; // Type `string` is assignable to type `unknown` ✅

//-----------

function g() {
  return Math.random() < 0.5 ? 42 : 'meh';
}
const h = f() as number | string | boolean; // Type `number | string` is assignable to type `number | string | boolean` ✅

//-----------

const person = {
    name: 'Chuck',
    lastName: 'Norris' as string | undefined // Type `Norris` is assignable to type `string | undefined`  ✅
}
person.lastName = undefined; // this is fine because of the type assertion (just showing why we'd want the `as string | undefined`)

Additional Info

Here's a link to a TS playground with the passing and (proposed) failing cases.

@lgenzelis lgenzelis added enhancement: new plugin rule New rule request for eslint-plugin package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin triage Waiting for team members to take a look labels Jul 8, 2023
@JoshuaKGoldberg JoshuaKGoldberg added blocked by external API Blocked by a tool we depend on exposing an API, such as TypeScript's Type Relationship API and removed triage Waiting for team members to take a look labels Jul 8, 2023
@JoshuaKGoldberg
Copy link
Member

I bet this would be a fascinating rule to figure out the details of... but it's blocked by the lack of a TypeScript assignability API: microsoft/TypeScript#9879.

As much as I love a good Chuck Norris reference, not much point to discussing here until we have a TypeScript assignability API. 😞

Related:

@lgenzelis
Copy link
Contributor Author

Wow, I didn't know such an API didn't exist. I can't imagine how hard it must be to write rules without having a tool as basic as isTypeAssignableTo :/

In the meantime, for my use case, I can just use a noop function that takes care of this. Something like:

function safeAs<T>(x: T) {
  return x;
}

This allows you to do safeAs<string | undefined>('some text'), but prevents you from doing

const x: string | number = 42;
const y = safeAs<string>(x);

Playground link here :)

Anyways, thanks for your quick response @JoshuaKGoldberg ! I leave the decision up to you as to whether it makes sense or not to keep this issue open.

@bradzacher
Copy link
Member

The satisfies operator will do the same check as a no-op function (but it doesn't change the type). A variable annotation also does the same safe checks (and it does change the type).

@lgenzelis
Copy link
Contributor Author

Thanks @bradzacher ! The issue with a variable annotation is that it requires a separate statement. I believe the place where I use as the most is in large objects in files with mock data for tests. I want to be able to have an object like

const bigObject = {
  name: 'Random',
  lastName: 'Guy',
  age: 42,
}

and then, in one test, do

bigObject.age = '42'; // BE sometimes sends age as a string

Since the objects are really large, I don't want to write the types myself. So, my "only" option is writing

const bigObject = {
  name: 'Random',
  lastName: 'Guy',
  age: 42 as string | number,
}

To use a variable annotation I would need to declare const age: string | number beforehand. And since I have many objects (and many properties which require these kind of annotations), doing that would be cumbersome 😅

@TSMMark
Copy link

TSMMark commented Oct 11, 2023

I would love to see a eslint rule to disallow as entirely, or prefer satisfies over as — or something along those lines.

Does such a rule exist? I searched around but couldn't find it. Would the proposal in this issue cover that? Or would maintainers prefer I open a separate issue?

@JoshuaKGoldberg
Copy link
Member

JoshuaKGoldberg commented Oct 11, 2023

#709 😢 blocked on microsoft/TypeScript#9879

@TSMMark
Copy link

TSMMark commented Oct 11, 2023

#709 😢 blocked on microsoft/TypeScript#9879

Thansk @JoshuaKGoldberg

I might take a custom linter into my own hands then. Do you foresee any issues if I wrote myself a custom eslint rule, which would simply look for the as operator and report an offense in 100% of cases?

In this codebase specifically, I'm okay saying that "if the author really knows what they're doing and needs to use as, they can eslint-disable directive comment for that line"

@bradzacher
Copy link
Member

@TSMMark for disallowing as see https://typescript-eslint.io/rules/consistent-type-assertions

@controversial
Copy link
Contributor

Note Typescript 5.4 made isTypeAssignableTo public:

@JoshuaKGoldberg created a discussion stating that it will be usable in the next major: #7936

Does this mean a no-unsafe-type-assertion rule will be possible to add to typescript-eslint v8?

@JoshuaKGoldberg
Copy link
Member

That should be right, yup! 🙌

@kirkwaiblinger
Copy link
Member

🎉 For anyone that comes along after v8 is released, consider this "accepting PRs" (and please tag one of us to poke us so we can change the label)

@JoshuaKGoldberg
Copy link
Member

Now that v8 is released, per #7936, we can mark this issue as unblocked! 🚀

@Samuel-Therrien-Beslogic
Copy link
Contributor

I should mention, if anyone's looking for inspiration, there's this implementation by a third-party plugin: https://github.com/danielnixon/eslint-plugin-total-functions/blob/master/src/rules/no-unsafe-type-assertion.ts

Also, it'd be nice to properly document and teach alternatives to unsafe type-casts, here's the note I had in our internal linting docs:

// It takes an advanced TypeScript user to fully make sense of this rule
/**
 * It can sometimes be hard to make sense of this rule, but let's be strict!
 * Tips:
 * - Prefer coercion over assertion: e.g `String(value ?? '')` over `value as string | null`
 *   (this depends on your functional logic)
 * - Try to assign to an explicitly typed variable and TypeScript will tell you about the type difference!
 *     ie: `const newValue: ExpectedType = unsafeValue`
 * - If using TypeScript 4.9+, consider using the `satisfies` Operator:
 *     (https://devblogs.microsoft.com/typescript/announcing-typescript-4-9/#the-satisfies-operator)
 *     `const newValue = {a: "A", b: 2} satisfies ExpectedType`
 * - If using TypeScript 4.9+, consider using the `in` Operator to narrow unlisted properties:
 *     (https://devblogs.microsoft.com/typescript/announcing-typescript-4-9/#unlisted-property-narrowing-with-the-in-operator)
 *     `if ("name" in unsafeValue && typeof unsafeValue.name === "string") ...`
 * - Sometimes just casting as `Partial<>` is sufficient
 * - Double check that you are not accidentally removing `null | undefined` from the type when casting:
 *   - `inputEvent.target as Partial<HTMLInputElement>` <-- Oops!
 *   - `event.target as Partial<HTMLInputElement> | null` <-- Ok!
 */

@github-actions github-actions bot added the locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. label Nov 22, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 22, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepting prs Go ahead, send a pull request that resolves this issue enhancement: new plugin rule New rule request for eslint-plugin locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants