Skip to content

Syntax Literal Type with Regular ExpressionsΒ #53673

Closed

Description

Suggestion

πŸ” Search Terms

RegExp, Regular Expressions, Syntax, Literal, Type, Autocomplete, Suggestions

βœ… 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.

⭐ Suggestion

This proposal would introduce a new feature which would allow for constraining the type of a string using Regular Expressions (RegExp/regex for short).
This feature could be used to only allow strings that follow a certain syntax (hence the name "Syntax Literal").

Proposed format:

type example = / /* RegExp to be used */ /

No flags should be used when writing a Syntax Literal type, nor should the characters ^ or $ be used (See Optional Implementations for more notes)
When checking a string against a Syntax Literal, it would be evaluated as if the literal's regex starts with ^ and ends with $ (ie. it would make sure that the entire string matches, instead of a portion of the string).
With the example defined above, the string "(literal)" would be acceptable, but the string "syntax (literal)" would throw an error.

Syntax Literals would be able to provide some similar functionality to template literals, but each would still have some benefits to the other (explained in Use Cases).

πŸ“ƒ Motivating Example

Say you are writing a function that will act as a simple calculator that can accept parentheses as long as they are not nested.

function calculator(input: string): number {
    // function body
}

const successfulInput = "2 * (3 + 4)"
const unsuccessfulInput = "2 * (3 + 4"

calculator(successfulInput) // this would not throw an error and would execute successfully
calculator(unsuccessfulInput) // this would still not throw an error, but would not execute successfully

The compiler would not catch that unsuccessfulInput would throw an error upon execution, and transpiles the code without error.
If the function were to be implemented with the proposed Syntax Literals, it would turn out something like this:

type InputSyntax = /(((\d|\.)+|\((\d|\.)+ [\+\-\*\/] (\d|\.)+\))( [\+\-\*\/] )?)+/
function calculator(input: InputSyntax): number {
    // function body
}

const successfulInput = "2 * (3 + 4)"
const unsuccessfulInput = "2 * (3 + 4"

calculator(successfulInput) // this would not throw an error and would execute successfully
calculator(unsuccessfulInput) // this would now throw an error, as unsuccessfulInput does not satisfy type InputSyntax

With this implementation, the compiler would catch that unsuccessfulInput does not match the required syntax, and would not transpile the code.
This is a hyper-specific and specialized example, but more generalized ones exist.
For an explanation/test of how the regex of InputSyntax works, go to regex101.

πŸ’» Use Cases

Template Literals already exist to accomplish some of this functionality. However, Template Literals have a limited precision in their capabilities.

Common Functionality Between Template and Syntax Literals

type syntax1 = /<.*>/
// would be equivalent to
type template1 = `<${string}>`

type syntax2 = /<(ab|cd|ef)>/
// would be equivalent to
type template2 = `<${"ab"|"cd"|"ef"}>`

Using Syntax Literals over Template Literals

Syntax Literals would be used in place of Template Literals in cases where more precision is required.

// You want text between parentheses, as long as that text does not contain additional parentheses
// ^(ie. you don't want nested parentheses)

type template3 = `${string} (${string})` // impossible to accomplish with Template Literals
type syntax3 = /.* \([^()]*\)/ // where Template Literals fail, Syntax Literals can succeed

Using Template Literals over Syntax Literals

Syntax Literals would have limited functionality, especially so as to not cause Template Literals to become obsolete. The primary superpower of a Template Literal is being able to use other types in its definition. Syntax Literals would not have this functionality, as this would potentially be a modification of ECMAScript's RegExp implementation. (See Optional Implementations for further notes on this)

type modes = "editing" | "commenting" | "viewing"
type email = `${string}@${string}.${string}`
type message = `User ${email} has ${modes} permissions`
// type message would be technically feasible with Syntax Literals, but Syntax Literals are unable to reference other types

type operation = "read" | "write"
class Clazz {}
class ChildOfClazz extends Clazz {}
type extensionTest<T extends Clazz> = `${T extends ChildOfClazz ? "Child" : "Base"} ${operation}`
// type extensionTest would be impossible to replicate with Syntax Literals

Optional Implementations

This section has been added as an unnecessary part of the suggestion's implementation. In other words, the following suggestions are merely "bonus functionality"

Using Syntax Literals in Template Literals

If possible, allow for template literals and syntax literals to be combined for more advanced/readable type checking.

// using the non-nested parentheses example mentioned earlier
type syntax3 = /.* \([^()]*\)/
// ^^ this could potentially be accomplished with Template Literals
type templateSyntax3 = `${string} ${/\([^()]*\)/}`

Using references to other Syntax Literals within Syntax Literals

If possible, modify the regex definition specifically in the case of Syntax Literals to allow for the special character combinations ^...$. These would be the Syntax Literal equivalent to the ${...} syntax of Template Literals. The reason why this is here instead of in the actual suggestion body is because it could potentially modify built-in javascript functionality.

type ops = /[\+\-\*\/]/
type num = /\d+(\.\d+)?/
type equationW2Ops = /^num$ ^ops$ ^num$ ^ops$ ^num$/

If this is possible, the implementation could potentially allow for other types and unions. This should be limited to only types and unions, and should not allow for other typescript keywords, such as extends and infer.

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

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions