镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Do not allow surrounding spaces to be counted as part of ${number} templates in template index strings  #46109

Description

Suggestion

🔍 Search Terms

Template String Pattern Index Signatures
Template Index Strings

✅ Viability 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, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

Actually it would change the typescript behavior as it was released - maybe better now than later?
The current behavior might be more bug than feature.

⭐ Suggestion

Not allowing spaces would reduce typing mistakes.

📃 Motivating Example

Modification of the example feature explanation in the handbook:

interface Options {
    width?: number;
    height?: number;
}

let a: Options = {
    width: 100,
    height: 100,
    "data-1": true, // Error! 'data-blah' wasn't declared in 'Options'.
};

interface OptionsWithDataProps extends Options {
    // Permit any property matching 'data-${number}'.
    [optName: `data-${number}`]: unknown;
}

let b: OptionsWithDataProps = {
    width: 100,
    height: 100,
    "data-1": true,       // Works!
    "data- 1": true,       // Passes but might be better off failing
    "data-1 ": true,       // Passes but might be better off failing
    "data- 1 ": true,       // Passes but might be better off failing
    "data- ": true,       // Passes but might be better off failing  (no number at all)

    "unknown-property": true,  // Error! 'unknown-property' wasn't declared in 'OptionsWithDataProps'.
};

playground

💻 Use Cases

Using it to catch unintended key entries.

Activity

  1. changed the title [-]Do not allow spaces around ${number} templates in template index strings [/-] [+]Do not allow surrounding spaces to be counted as part of ${number} templates in template index strings [/+] on Sep 28, 2021
  2. IllusionMH commented on Sep 28, 2021

    @IllusionMH
    Contributor

    Can be simplified to

    const a: `data-${number}` = "data-1"; // OK
    
    // No errors
    const a1: `data-${number}` = "data- 1";
    const a2: `data-${number}` = "data-1 ";
    const a3: `data-${number}` = "data- 1 ";
    const a4: `data-${number}` = "data- ";

    Playground

  3. jcalz commented on Sep 28, 2021

    @jcalz
    Contributor

    Oh wow this an even better example for my list of surprising behaviors of `${number}` in #41893! I'm still not quite able to internalize the interpretation of `${number}` meaning "some string that converts to a finite number", but apparently that's what it means (as opposed to "the type of `${n}` when n is a finite number").

  4. craigphicks commented on Sep 28, 2021

    @craigphicks
    Author

    Joe Calzaretta (@jcalz) - Thanks for link to 7.1.4.1.2 Runtime Semantics: StringNumericValue, that is very informative. So the behavior is intended to match some formal standard - as I suspected but wasn't sure. (I also enjoyed you list of examples!)

    For a user on the consuming end of the data, if they want to correctly decode the pattern suffix to disambiguate the many-to-one mapping of strings to numbers, they must use of algorithm listed in [7.1.4.1.2 Runtime Semantics: StringNumericValue]. I see that a possible source of confusion and errors. That risk would be mitigated by employing a simple one-to-one mapping.

    Having a many-to-one mapping of keys strings to semantic keys, before using the keys to lookup the value in the object map seems anti intuitive and error prone. But the original problem lies in JS, not TS.

    On the other hand, if TS is going to impose some strict typing on the keys for arbitrary integers, why not go the whole way and allow specification of a one-to-one map? For an explicit limited range of integers it is already possible:

    type CountingIntegers = "1"|"2";
    type FooN = Record<`foo${CountingIntegers}`,any>
    

    Maybe the feature request should be for a temple-key-string use specific intrinsic utility type "CountingIntegers" allowing all nonnegative integers up to Number.MAX_SAFE_INTEGER (or maybe convertible to bigint). That is narrow enough to be safe and easy to implement, is extremely simple for a user to understand completely at a glance, and has practical value in eliminating misunderstandings.

  5. craigphicks commented on Sep 28, 2021

    @craigphicks
    Author

    Thanks to Joe Calzaretta (@jcalz) pointing out 7.1.4.1.2 Runtime Semantics: StringNumericValue, it is now clear within a shadow of a doubt that the "surrounding space" behavior is "working as intended". So I will close this issue.

  6. jcalz commented on Sep 28, 2021

    @jcalz
    Contributor

    I’m not sure… I’d really like to hear someone officially say that this is intentional. I’m just an interested bystander.

  7. IllusionMH commented on Sep 28, 2021

    @IllusionMH
    Contributor

    Same here regarding whitespace.
    While 100.1 or even 1e3 are really looking as number literals including whitespace doesn't look expected from usage perspective, but that's IMHO.

  8. andrewbranch commented on Sep 28, 2021

    @andrewbranch
    Member

    This is very surprising to me and seems undesirable, but I’ll let Anders Hejlsberg (@ahejlsberg) have the definitive word.

  9. ahejlsberg commented on Sep 29, 2021

    @ahejlsberg
    Member

    Yeah, we're basically just relying on JavaScript's intrinsic string-to-number conversion to validate `${number}` placeholder values--and it permits leading and trailing whitespace. Also, strangely, it permits empty strings and strings with only whitespace and returns 0 for those.

    I agree it makes more sense to not permit any whitespace and in particular to not permit empty strings. It's technically a breaking change, but can't imagine many types depend on it.

  10. HansBrende commented on Jan 31, 2025

    @HansBrende
    Contributor

    Joe Calzaretta (@jcalz)

    I'm still not quite able to internalize the interpretation of ${number} meaning "some string that converts to a finite number"

    And I'm still hung up on "finite"!

    To me, this behavior also seems strange:

    1e999 satisfies number  // Passes!
    `${1e999}` satisfies `${number}`  // Error: Type "Infinity" does not satisfy the expected type `${number}`
    
    type Oops<N extends number, IsItReallyFinite extends `${number}` = `${N}`> = IsItReallyFinite
    
    'Infinity' satisfies Oops<1e999>  // Passes...

    I'd expect the correct definition to be "some non-empty, trimmed string that converts to a non-NaN number".

    Either that, or the type of `${N}` where N extends number should widen to `${number}` | 'Infinity' | '-Infinity'. (Either way would resolve the inconsistency, but the former seems like the obvious approach.)

  11. nnmrts commented on Jan 6, 2026

    @nnmrts

    Yeah, we're basically just relying on JavaScript's intrinsic string-to-number conversion to validate `${number}` placeholder values--and it permits leading and trailing whitespace. Also, strangely, it permits empty strings and strings with only whitespace and returns 0 for those.

    I agree it makes more sense to not permit any whitespace and in particular to not permit empty strings. It's technically a breaking change, but can't imagine many types depend on it.

    Anders Hejlsberg (@ahejlsberg)

    Okay, but why is TypeScript then handling empty strings and whitespace strings differently?

    type ThisIsTrue = " " extends `${number}` ? true : false;
    type ThisIsFalse = "" extends `${number}` ? true : false;
    
    const thisIsZero = Number(" ");
    console.log(thisIsZero);
    
    const thisIsAlsoZero = Number("");
    console.log(thisIsAlsoZero);

    https://www.typescriptlang.org/play/?#code/C4TwDgpgBAKgFgSwM4EkkwE4FdoF4oBEhUEAHsBAHYAmSUABgCQDelWAtgEYQYC+9UAPxRg2aAC4oAMwCGAGyQQA3AChQkWIlRIAYvMVR8BImQo06TVh258Bw0TiiTZC5SpUBjAPaUkwEVpoAFo8XoZQAHLWPAAURAQAlKrevl5yEAB0cl4A5jHAgUghGF5J7il+AchoAIIKXsVh+FFcscZlFWmZ2XkF1Uh1SA2hSUA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Needs InvestigationThis issue needs a team member to investigate its status.RescheduledThis issue was previously scheduled to an earlier milestone

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions