Skip to content

Enhancement: [no-base-to-string] deprecate restrict-template-expression into this rule #9644

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
4 tasks done
abrahamguo opened this issue Jul 26, 2024 · 11 comments
Closed
4 tasks done
Labels
enhancement: plugin rule option New rule option for an existing eslint-plugin rule 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 wontfix This will not be worked on

Comments

@abrahamguo
Copy link
Contributor

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

My proposal is suitable for this project

  • I believe my proposal would be useful to the broader TypeScript community (meaning it is not a niche proposal).

Link to the rule's documentation

https://typescript-eslint.io/rules/no-base-to-string/

Description

The description of no-base-to-string from the docs is

JavaScript will call toString() on an object when it is converted to a string, such as when + adding to a string or in ${} template literals. The default Object .toString() uses the format "[object Object]", which is often not what was intended. This rule reports on stringified values that aren't primitives and don't define a more useful .toString() method.

and the description for restrict-template-expressions is

JavaScript automatically converts an object to a string in a string context, such as when concatenating it with a string using + or embedding it in a template literal using ${}. The default toString() method of objects uses the format "[object Object]", which is often not what was intended. This rule reports on values used in a template literal string that aren't strings, optionally allowing other data types that provide useful stringification results.

It seems to me that, especially with the allowArray option I recently added to restrict-template-expressions and the upcoming allow option in #8556, restrict-template-expressions is becoming much more similar to no-base-to-string except that restrict-template-expressions only applies to interpolation while no-base-to-string covers interpolation, concatenation, and explicit toString calls.

From @JoshuaKGoldberg on #8556:

This rule is getting more similar to no-base-to-string. Is that OK? Do we combine the rules?

Good question... I've talked about it in the past with @bradzacher but don't recall where.

IIRC the main points against were that the rules technically do two different ideas: one restricts what you pass to template expressions; the other more broadly prevents odd .toString()s.

Personally, I'm not opposed to eventually unifying the two rules. It'd be nice to not have to manage two of them - especially now that, as you've noted, they've grown much more similar...

Fail

All existing examples from `no-base-to-string` and `restrict-template-expressions`.

Pass

All existing examples from `no-base-to-string` and `restrict-template-expressions`.

Additional Info

No response

@abrahamguo abrahamguo added enhancement: plugin rule option New rule option for an existing eslint-plugin rule package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin triage Waiting for team members to take a look labels Jul 26, 2024
@JoshuaKGoldberg
Copy link
Member

Yeah I've found it more and more confusing over the years when to use which rule. They're technically different rules but generally cover the same area of responsibility. I struggle to imagine a case where a project would strongly want one and not the other. 👍 from me with a request for more input from @typescript-eslint/triage-team.

@Josh-Cena
Copy link
Member

Josh-Cena commented Aug 22, 2024

no-base-to-string is a correctness rule. There's no reason you would disable it, as the reports indicate actual bugs in your code ("[object Object]" showing up). (There are unfixable bugs with this rule too, like certain types lacking toString() declaration, but you can configure them away.)

restrict-template-expression, and its sister restrict-plus-operands, are stylistic rules. While their union catches a subset of the mistakes caught by no-base-to-string, their main purpose is to allowlist displayable types, while no-base-to-string creates a blocklist of non-displayable types. By this nature, not everyone would be happy with these two rules (especially when we restrict common things like never and arrays by default, as well as many others that rely on implicit string coercion).

@bradzacher
Copy link
Member

Yeah there's significant overlap but there's also distinction. Josh's description is apt.

I think a good summary - no-base-to-string is about code correctness, restrict-template-expressions is about presentational correctness.

It's often that I'd disable the latter but I'd rarely disable the former.

@JoshuaKGoldberg
Copy link
Member

JoshuaKGoldberg commented Aug 23, 2024

restrict-template-expression, and its sister restrict-plus-operands, are stylistic rules

Should we move them into the stylisticTypeChecked config then? (I'd think not, they catch actual bugs)

I think my root issue with the no-base-to-string rule is that both its name and functionality appear to be roughly the union of restrict-plus-operands & restrict-template-expressions.

While their union catches a subset of the mistakes caught by no-base-to-string

Just confirming: it looks to me like the only part of no-base-to-string not covered by the other two rules is explicit .toString() calls. Am I missing anything?

I'm wondering now, if we remove the plus operand and template expression checking from no-base-to-string, would the three rules no longer have any overlap? And if so, would that fix the common confusions around them?

@Josh-Cena
Copy link
Member

Just confirming: it looks to me like the only part of no-base-to-string not covered by the other two rules is explicit .toString() calls. Am I missing anything?

It would, by principle, catch other things that do stringification, including #4314 and #3388.

I'm wondering now, if we remove the plus operand and template expression checking from no-base-to-string, would the three rules no longer have any overlap? And if so, would that fix the common confusions around them?

No, because then you would not have a rule that only enforces "no "[object Object]" in string values". Again: the two stylistic rules are too strict to be considered correctness only (they have correctness components, sure, but they check beyond that). If we add options like allowCustomStringifiers to these two rules, then it may work, though in general I don't find it appetizing.

@JoshuaKGoldberg
Copy link
Member

JoshuaKGoldberg commented Aug 23, 2024

Just confirming: it looks to me like the only part of no-base-to-string not covered by the other two rules is explicit .toString() calls. Am I missing anything?

It would, by principle, catch other things that do stringification, including #4314 and #3388.

Ah, gotcha, in that case we can consider my proposal to be explicit .toString() calls and similar, such as String(...).

they check beyond that

Just for clarity, what do you see as the stylistic points beyond the correctness checking?

I'm having a hard time conceptualizing why console.log("Value: " + []) is different from console.log(`Value: ${[]}`) (and equivalent array/etc. values).

@Josh-Cena
Copy link
Member

Josh-Cena commented Aug 23, 2024

The point is that console.log(`Value: ${{ toString: () => "Hi" }}`) should be allowed by default, which is the case with no-base-to-string. Banning it and requiring an explicit String() or toString() is a stylistic preference.

This proposal asks us to merge restrict-template-expression into this rule, which would be even more confusing, since no-base-to-string, according to its name, forbids one thing: the use of Object.prototype.toString. The only change I find sensible is to amalgamate all of them into a single rule, no-unexpected-stringification, which:

  • Bans the use of Object.prototype.toString under any circumstance
  • Bans implicit string coercion, behind an option

I agree with you that restrict-template-expression and restrict-plus-operands should probably be a single rule, though.

@JoshuaKGoldberg
Copy link
Member

😅 I'm having a hard time keeping track of the different areas of concerns across:

  • Logical vs. stylistic
  • Object.prototype.toString vs implicit string coercion
  • Numeric vs. stringification

...given that this impacts at least:

  • no-base-to-string
  • restrict-plus-operands
  • restrict-template-expressions

I also personally am focusing on other work around project services and Promises over this issue area.

It'd be useful if someone could summarize all the things that we would want to lint for, along with whether they're logical or stylistic, and which rule(s) catch them now.

@Josh-Cena
Copy link
Member

Josh-Cena commented Sep 1, 2024

This is a stylistic case:

const obj = { toString: () => "foo" };
`${obj}`; // restrict-template-expressions
`${obj.toString()}`; // all happy
obj + ""; // restrict-plus-operands
obj.toString(); // all happy

This is a correctness case:

const obj = {};
`${obj}`; // restrict-template-expressions + no-base-to-string
`${obj.toString()}`; // no-base-to-string
obj + ""; // restrict-plus-operands + no-base-to-string
obj.toString(); // no-base-to-string

This is a stylistic (arguably) case:

const obj = 1;
`${obj}`; // restrict-template-expressions
`${obj.toString()}`; // all happy
obj + ""; // restrict-plus-operands
obj.toString(); // all happy

TLDR is that no-base-to-string catches a well-defined set of cases known to be wrong, while restrict-* allows a well-defined set of cases known to be right.

@JoshuaKGoldberg
Copy link
Member

TLDR is that no-base-to-string catches a well-defined set of cases known to be wrong, while restrict-* allows a well-defined set of cases known to be right.

This delineation I like. I also like the delineation from #9644 (comment) as one area being logical, the other stylistic.

Proposal @typescript-eslint/triage-team: let's mark this as accepting PRs for our next breaking-changes-allowed major to:

  • Solidify the general concatenation/stringification/template-strings rules into two rules
  • Have one of those rules be logical, the other stylistic
  • Write detailed docs and a blog post explaining the history of them

?

@JoshuaKGoldberg
Copy link
Member

We chatted internally. There doesn't seem to be consensus on a best way to delineate these rules' areas of functionality. It's not really clearly split between "logical" and "stylistic". I'm out of ideas and pretty much fine having them remain as-is.

We're pretty certain that if something does happen, it won't be a rule deprecation. If anybody else has a clear proposal for how to do things, please do feel free to file a new issue. This one is getting long 😅.

Thanks all for the discussion!

@JoshuaKGoldberg JoshuaKGoldberg closed this as not planned Won't fix, can't repro, duplicate, stale Dec 30, 2024
@JoshuaKGoldberg JoshuaKGoldberg added wontfix This will not be worked on and removed triage Waiting for team members to take a look labels Dec 30, 2024
@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 Jan 7, 2025
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jan 7, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement: plugin rule option New rule option for an existing eslint-plugin rule 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 wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

4 participants