Template strings: Negated match
See original GitHub issueSuggestion
š 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.
Issue Analytics
- State:
- Created a year ago
- Reactions:14
- Comments:8 (3 by maintainers)
Top GitHub Comments
Thinking about this more, I wonder if a lot of the concerns about the original
not
proposal could be addressed by starting with a more limited feature only for primitive types and literals:true
,0
,""
,`${}`
, etcnull
,boolean
,number
,bigint
,string
,symbol
)any
,never
,unknown
, objects, etc)From the original pull request (#29317), from what I can gather, these were the primary concerns: (cc @DanielRosenwasser)
Consistency: Can this feature compose with every other feature in TS and get expected results?
Complexity: This is going to have to be considered in every future change to TypeScript, and could get in the way of more useful features.
Use cases: A lot of the use-cases for this feature can already be implemented in other (less declarative) ways
Flexibility around primitive and literal types is the main use case that Iām concerned with, and I think I would actually prefer if TypeScript told me when I was trying to do silly stuff like
not any
because that sounds like a mistake to me.If we presuppose
not T
as the type negation operator, then itās tempting to support this (initially) viawhich we can parse only in the context of a
${ ... }
placeholder. Thoughts?