-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
const TemplateStringsArray for TaggedTemplateExpression #31422
Comments
+1 on this. This (superficially, at least) seems like a very small change that could have major benefits. It would be really useful to be able to construct simple DSLs using tagged template strings with types that could vary depending on the literal string arguments. A trivial example: type Meters = Brand<number, 'meters'>;
function withUnits(literals: [' + '], a: Meters, b: Meters): Meters;
function withUnits(literals: [' - '], a: Meters, b: Meters): Meters;
function withUnits(literals: [' * '], a: number, b: Meters): Meters;
function withUnits(literals: [' * '], a: Meters, b: number): Meters;
function withUnits(literals: [' / '], a: Meters, b: Meters): number;
function withUnits(literals: [' + ' | ' - ' | ' * ' | ' / '], a: any, b: any): any {
switch (literals[0]) {
case ' + ': return a + b;
case ' - ': return a - b;
case ' * ': return a * b;
case ' / ': return a / b;
}
}
const a = 100 as Meters;
const b = 200 as Meters;
const foo: Meters = withUnits`${a} + ${b}`;
const bar: number = withUnits`${a} / ${b}`; This doesn't constitute a solution to #364, but a more sophisticated version of the above might make for a more convenient way to use type-safe units in TypeScript. That's just one example of how enabling this kind of simple type-safe DSL may be useful. |
It's been a while without activity on this, I am generating localization strings from a JSON file and it would be really helpful in those cases too type DictEntry = "greetings.hello" | "actions.success"
interface Props {
name: string
}
const translate = ([str]: TemplateStringsArray, obj: Props) => { }
console.log(translate`greetings.hello ${{ name: "Daniell" }}`) |
@RyanCavanaugh I see you added the "awaiting more feedback" label to this issue, so here I am 🤓 I completely agree with @xialvjun's suggestion Let's say I want to create a tag which returns an object containing a prop, which is drawn from the first string of the declare function createObjFromFirstTag<T extends TemplateStringsArray>(
tags: T,
): {
e: T[0];
}; One would think that createObjFromFirstTag`some text`; Expected Type {e: "some text"} Actual Type {e: string} With variadic tuple type support coming in 4.0 (🥳), it––imho––seems like this suggestion is also fit for the release. Tagged templates are such a powerful tool for library developers. It could be very useful to the end developer to enable type-checking of supplied values as literals. |
This would be largely beneficial for SDL library developers. What's blocking the implementation of this narrowing? |
I would be extremely appreciative of any more thoughts from the community & TS team! Seems like a very valuable feature. |
I see many people just focus on
|
@xialvjun I 100% agree. Having the types narrowed paves the way for extraordinary typescript-based DSL experiences. I was originally thinking this would enable cool JSX alternatives. const MyComponent = Tree(_ => _
_`div`(
_`span`("Hello World"),
), propsTree);
// can be type-checked according to valid props for the given element (specified above within tags)
const propsTree = {
className: "container",
children: [
{
className: "green",
},
],
} as const; Now I'm imagining the implications for the GraphQL use case: const schema = Schema(_ => _
.scalar<Date>`Date`
.type`User`(
_`name``String`,
_`dob``Date`
)
.type`Query`(
_`users``List``User`,
)
); From the schema definition above, one could create type-safe resolver implementations and document builders, all from a single schema definition. const impl = schema.impl<Ctx>({
query: {
users: (_parent, _args, _ctx) => {/**/}
}
});
const document = schema.doc(_ => _.users(_ => _.name().dob())); This experience would be quite nice! Especially in contrast with code-gen heavy, GQL-first approaches. |
Just as another, related example of a use case: the use case I'm thinking of has to do with making "code-gen heavy, GQL-first approaches" a little less verbose! At the moment, using those codegen solutions generally looks like this:
With this feature, it'd be possible to just write the first line, and then codegen an overloaded type for |
@excitedleigh I'd love to hear more about your idea for a TS-first GQL experience, and how type-safe tagged template expressions would enable that experience. A more detailed explanation of the use case could potentially help us get buy-in. Gearing up for the release of 4.0, I imagine that TS team members are dealing with quite a lot at the moment. Hopefully we can make a good-enough argument to get this fix/feature on the roadmap soon-after :) |
Alright, I'll try to elaborate if I can. The current state of affairsAt current, using something like Apollo with its TypeScript codegen tool, I'd write something like this: const QUERY = gql`
query OrderQuery {
branches {
id
branchName
}
orders(first: 20) {
id
...ExistingOrderItem_order
branch {
id
}
}
}
${ExistingOrderItem.fragments.order}
` At current, import { OrderQuery } from "./__generated__/OrderQuery"
const orders = useQuery<OrderQuery>(QUERY) If the import { OrderQuery, OrderQueryVariables } from "./__generated__/OrderQuery"
const orders = useQuery<OrderQuery, OrderQueryVariables>(QUERY) ...which is even more verbose. What I'd like to be able to buildI'd like to be able to change the codegen process, so instead of it generating the declare function gql(text: readonly [
"\n query OrderQuery {\n branches {\n id\n branchName\n }\n orders(first: 20) {\n id\n ...ExistingOrderItem_order\n branch {\n id\n }\n }\n }\n ",
"\n",
], values: readonly [typeof ExistingOrderItem.fragments.order]): GraphQLQuery<
{ /* calculated type of variables goes here */ },
{ /* calculated type of resulting data values goes here */},
> That definition would go in a Then, Does that help? PS: congrats on the impending 4.0 release! I'm especially excited about the tuple spread type stuff :) |
@excitedleigh that makes great sense! Thank you for elaborating on your idea for a DX which would be made possible. |
I wanted to (hopefully) re-draw attention to this issue, as I do believe this slight type-safety enhancement could result in some extraordinary experiences. |
With the template literal type features of 4.1, I believe this narrowing is more important than when this issue was first opened. Tagged template library developers will want to...
|
I just checked and saw that this issue is not labeled with "Bug". I believe this is a mistake. A tagged template is just a function. One can even call it as such. declare function myTag<T extends TemplateStringsArray>(tags: T): T[0];
myTag(["first"]); // type: "first" Why would tagged templates (mere functions) behave differently when it comes to narrowing argument types? This is inconsistent. I'm curious whether others also feel that this issue merits the "Bug" label? |
@harrysolovay It is clearly working as intended, as |
@voxpelli, specifying I'd also urge you to communicate the "why?" What do you mean by:
Moreover...
This is actually not clear from your description. I'd love to hear your thoughts more in depth! |
Actually, you should use |
#33304 is a similar issue, and in that one it shows how Template String types would allow for doing things like const div = html`<div>...</div>` // div would have implicit type HTMLDivElement
const p = html`<p>...</p>` // p would have implicit type HTMLParagraphElement |
Dear TS team, I'd like to reiterate:
Currently, the above is not possible. Any thoughts would be greatly appreciated! |
+1 for this feature. I have a case where I’m writing typings for a Flow library, where we could have safer typings thanks to template literal types, except the pattern the library uses is to have a tagged template function. I can’t really change their pattern. The example can be found here, where |
The htm library could also use this functionality to enforce types within its hyperscript DSL. |
We were able to build something similar using graphql-code-generator: https://www.graphql-code-generator.com/plugins/gql-tag-operations-preset However, currently, it requires using |
I'm surprised that this issue has stagnated. Any updates? |
Search Terms
TemplateStringsArray, TaggedTemplateExpression
Suggestion
There shouldn't be a type
TemplateStringsArray
, it should be aconst string tuple type
, so we can write code:Use Cases
Just like in the above code, we can test the sql at compile time or use a TypeScript Language Service Plugin (in fact, I'm writing itts-sql-plugin and then come across this problem).
Examples
Checklist
My suggestion meets these guidelines:
There is another issue is alike. #16552 (comment)
Besides,
TemplateStringsArray
isReadonlyArray<string>
, it should be const anyway.The text was updated successfully, but these errors were encountered: