Repository: TypeScript
Issue: Template strings: Negated match
Labels: Suggestion,In Discussion

# Suggestion

## 📃 Motivating Example

Strings, in particular key names, sometimes affect types based on the format of the string, most commonly with prefixes. One such example is the recent [W3C Design Tokens spec](https://design-tokens.github.io/community-group/format/#character-restrictions) which uses a `$` prefix to reserve key names like `$description` and `$type`.

Right now it's possible to create a positive match such as:

```ts
type CssCustomPropertyName = `--${string}`
```

But there's no way to create a negative match. Or in regex terms:

```regex
(?!--)   # not `--`
```

The goal here wouldn't be to recreate all the functionality of regex/parsing, it would be to handle the stringy-typing sometimes seen in JavaScript (including within official web specifications).

## ⭐ Suggestion

> **Note:** This is one option, I'm not particularly tied to it and could suggest alternatives if this is not workable.

Expose, in a limited capacity, the `not` operator (#29317) so that it can be used to filter strings.

```ts
type Name = string & not `--${string}`

let a: Name = "--x" // err!
let b: Name = "--" // err!
let c: Name = "x" // ok
let d: Name = "x--" // ok
let e: Name = "-x" // ok
```

There is an existing PR adding a `not` operator, but has been long stalled on expected behavior. But maybe just this one piece could be added and slowly expanded from, if desired, later.

## 💻 Use Cases

The [W3C Design Tokens spec](https://design-tokens.github.io/community-group/format/#character-restrictions) does not allow token/group names starting with a `$` or containing any of the characters `.{}`.

Using negated string matches, you could correctly type this:

```ts
type Name = string & not `$${string}`

interface Group {
	$description?: string;
	$type?: string;
	[key: Name]: Token | Group
}
```

The closest you can get to this today is:

```ts
type LowerAlpha = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'
type UpperAlpha = Uppercase<LowerAlpha>
type Numeric = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type AlphaNumeric = LowerAlpha | UpperAlpha | Numeric
type SafeSymbols = "-" | "_" | " "

type NameStart = AlphaNumeric | SafeSymbols
type Name = `${NameStart}${string}`

type Group = {
    $description?: string;
    $type?: string;
} & {
    [key in Name]: Group
}

let group: Group = {
    $description: "",
    $type: "",
    "my cool name": {}
}
```

## 🔍 Search Terms

List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.

- String
- Negated
- Pattern
- Regex
- Regular Expression
- Not
- Prefix
- Suffix

## ✅ Viability Checklist

My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).