Description
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 which uses a $
prefix to reserve key names like $description
and $type
.
Right now it's possible to create a positive match such as:
type CssCustomPropertyName = `--${string}`
But there's no way to create a negative match. Or in regex terms:
(?!--) # 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.
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 does not allow token/group names starting with a $
or containing any of the characters .{}
.
Using negated string matches, you could correctly type this:
type Name = string & not `$${string}`
interface Group {
$description?: string;
$type?: string;
[key: Name]: Token | Group
}
The closest you can get to this today is:
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:
- 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.