Skip to content

Add missing toString declarations for base types that have them #38347

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

Open
5 tasks done
bradzacher opened this issue May 5, 2020 · 7 comments
Open
5 tasks done

Add missing toString declarations for base types that have them #38347

bradzacher opened this issue May 5, 2020 · 7 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@bradzacher
Copy link
Contributor

Search Terms

toString, RegExp, Regular Expression, Boolean, Error

Suggestion

In @typescript-eslint, we have a rule no-base-to-string. This rule ensures that any value that's being coerced to a string has a toString method defined on it.
This is to help catch cases where you accidentally coerce something to a string, and end up with an [object Object] in your strings.

This rule has a pretty simple implementation - it just gets the declarations of the toString method on the type, and ensures that none of the declarations belong to the Object type.
The logic relies upon the fact that every type that actually has a toString method explicitly declares a toString method.

A few of our users ran into cases that were reporting errors for them, even though they shouldn't (typescript-eslint/typescript-eslint#1655).
There's one that specifically has caused people some issues: Boolean/boolean.

I eyeballed the docs and the types, and I am pretty certain there are only 3 types that are missing a toString declaration:

Related

PR: #37839

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels May 8, 2020
@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented May 8, 2020

In general we don't like modifying the built-in definitions unless there's a good reason (it's quite dangerous), and frankly "This lint rule is relies on incorrect assumptions about TypeScript's type system" doesn't seem like a good reason. Why not just add exclusions for those three types you listed?

@bradzacher
Copy link
Contributor Author

I understand that there is a chance for false positives that may occur, and much slimmer chance for a false negative, but in userland when it reports an error, it's going to be on an object type. And likely the fix isn't going to be "add a toString def to the object type definition", but is instead going to be "fix the code so it doesn't attempt to coerce an object to a string".

OOC, why is it quite dangerous to modify them in an additive fashion? I can understand changes and deletions being dangerous, but shouldn't additions be mostly safe?

We can add exceptions, it's just that the exceptions are somewhat cumbersome to build and maintain. A naive name-based exclusion won't work well, as it will also exclude user-defined types that shadow these builtins, so instead it means we have to do something like asserting that the declaration for the type exists in a library file - which is a large branch from the logic as it stands that doesn't get the type's declaration.

cc @JoshuaKGoldberg - the original rule author.

@RyanCavanaugh
Copy link
Member

The danger is really in the unknown unknowns. We generally find unexpected and unanticipatable breaks for any change to widely-used built in types.

@cnshenj
Copy link

cnshenj commented May 27, 2020

In this case, the built-in declaration is wrong, why it shouldn't be fixed?
Boolean type has a toString method: Boolean.prototype.toString(). Even without considering linting, I can't write true.toString(), which is perfectly valid JavaScript and (should be) TypeScript code.

@Oaphi
Copy link

Oaphi commented Apr 9, 2021

Regarding the missing definition for the Boolean interface, it would also be nice if the return type of the method was defined as a string literal union "true" | "false" and not just string (as in the PR) since even the spec considers "true" and "false" to be the only possible outcomes of step 2 of the method algorithm.

This would allow patterns like this (valid in JS) without having to resort to declaration merging in user code:

type SorterMap<T> = Record<"true" | "false", (a: T, b: T) => number>;

const makeTdStrSorter = ({ up = true }) => <
  T extends string
>(
  a: T,
  b: T
) => {
  const dirMap: SorterMap<T> = {
    true: (a, b) => (a < b ? -1 : a > b ? 1 : 0),
    false: (a, b) => (a > b ? -1 : a < b ? 1 : 0),
  };

  return dirMap[up.toString()](a, b); //expression of type 'string' can't be used to index type 'SorterMap<T>'
};

I do understand and appreciate the concern that changes to the standard library may cause things to break, but this is about adding something that should've probably been there from the get-go, so if it does break something, it is better to know it now than further down the road.

@kagankan
Copy link

I added toString in my d.ts file for this problem.

export {};

declare global {
  interface Boolean {
    toString(): string;
  }
}

I hope this problem will be fixed in the library side. 🥺

@CodingWatchCollector
Copy link

In this case, the built-in declaration is wrong, why it shouldn't be fixed? Boolean type has a toString method: Boolean.prototype.toString(). Even without considering linting, I can't write true.toString(), which is perfectly valid JavaScript and (should be) TypeScript code.

I've encountered the same problem with Boolean.toString() method and it was kinda frustrating to get an error for this method, especially for junior developer (I thought I've been doing something wrong).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

6 participants